Chromium Code Reviews| Index: webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
| diff --git a/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java b/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
| index 6e66b08395ca78e26a72887ce831ab6c4cf232d3..c3d194c63ae3f898958e503df271656b0e628139 100644 |
| --- a/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
| +++ b/webrtc/examples/androidapp/src/org/appspot/apprtc/AppRTCAudioManager.java |
| @@ -22,8 +22,11 @@ import android.media.AudioManager; |
| import android.preference.PreferenceManager; |
| import android.util.Log; |
| +import org.webrtc.ThreadUtils; |
| + |
| import java.util.Collections; |
| import java.util.HashSet; |
| +import java.util.List; |
| import java.util.Set; |
| /** |
| @@ -39,22 +42,45 @@ public class AppRTCAudioManager { |
| * AudioDevice is the names of possible audio devices that we currently |
| * support. |
| */ |
| - // TODO(henrika): add support for BLUETOOTH as well. |
| - public enum AudioDevice { |
| - SPEAKER_PHONE, |
| - WIRED_HEADSET, |
| - EARPIECE, |
| + public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE } |
| + |
| + /** AudioManager state. */ |
| + public enum AudioManagerState { |
| + UNINITIALIZED, |
| + PREINITIALIZED, |
| + RUNNING, |
| } |
| + /** Selected audio device change event. */ |
| + public static interface AudioManagerEvents { |
| + // Callback fired once audio device is changed or list of available audio devices changed. |
| + void onAudioDeviceChanged( |
| + AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices); |
| + } |
| + |
| + private final ThreadUtils.ThreadChecker threadChecker = new ThreadUtils.ThreadChecker(); |
| + |
| private final Context apprtcContext; |
| - private final Runnable onStateChangeListener; |
| - private boolean initialized = false; |
| private AudioManager audioManager; |
| + |
| + private AudioManagerEvents audioManagerEvents; |
| + private AudioManagerState amState; |
| private int savedAudioMode = AudioManager.MODE_INVALID; |
| private boolean savedIsSpeakerPhoneOn = false; |
| private boolean savedIsMicrophoneMute = false; |
| + private boolean hasWiredHeadset = false; |
| + |
| + // Default audio device; speaker phone for video calls or earpiece for audio |
| + // only calls. |
| + private AudioDevice defaultAudioDevice; |
| + |
| + // Contains the currently selected audio device. |
| + private AudioDevice selectedAudioDevice; |
| - private final AudioDevice defaultAudioDevice; |
| + // Contains user selected audio device. |
| + // TODO(henrika): always set to AudioDevice.NONE today. Add support for |
|
magjed_webrtc
2016/12/10 19:34:44
Is this comment correct? |userSelectedAudioDevice|
henrika_webrtc
2016/12/12 14:13:25
Updated the comments. In short, selectedAudioDevic
|
| + // explicit selection based on choice by userSelectedAudioDevice. |
| + private AudioDevice userSelectedAudioDevice; |
| // Contains speakerphone setting: auto, true or false |
| private final String useSpeakerphone; |
| @@ -65,8 +91,8 @@ public class AppRTCAudioManager { |
| // available, far from ear <=> use speaker phone). |
| private AppRTCProximitySensor proximitySensor = null; |
| - // Contains the currently selected audio device. |
| - private AudioDevice selectedAudioDevice; |
| + // Handles all tasks related to Bluetooth headset devices. |
| + private final AppRTCBluetoothManager bluetoothManager; |
| // Contains a list of available audio devices. A Set collection is used to |
| // avoid duplicate elements. |
| @@ -78,8 +104,10 @@ public class AppRTCAudioManager { |
| // Callback method for changes in audio focus. |
| private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener; |
| - // This method is called when the proximity sensor reports a state change, |
| - // e.g. from "NEAR to FAR" or from "FAR to NEAR". |
| + /** |
| + * This method is called when the proximity sensor reports a state change, |
| + * e.g. from "NEAR to FAR" or from "FAR to NEAR". |
| + */ |
| private void onProximitySensorChangedState() { |
| if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) { |
| return; |
| @@ -92,29 +120,54 @@ public class AppRTCAudioManager { |
| if (proximitySensor.sensorReportsNearState()) { |
| // Sensor reports that a "handset is being held up to a person's ear", |
| // or "something is covering the light sensor". |
| - setAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE); |
| + setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.EARPIECE); |
| } else { |
| // Sensor reports that a "handset is removed from a person's ear", or |
| // "the light sensor is no longer covered". |
| - setAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); |
| + setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE); |
| } |
| } |
| } |
| - /** Construction */ |
| - static AppRTCAudioManager create(Context context, Runnable deviceStateChangeListener) { |
| - return new AppRTCAudioManager(context, deviceStateChangeListener); |
| + /* Receiver which handles changes in wired headset availability. */ |
| + private class WiredHeadsetReceiver extends BroadcastReceiver { |
| + private static final int STATE_UNPLUGGED = 0; |
| + private static final int STATE_PLUGGED = 1; |
| + private static final int HAS_NO_MIC = 0; |
| + private static final int HAS_MIC = 1; |
| + |
| + @Override |
| + public void onReceive(Context context, Intent intent) { |
| + int state = intent.getIntExtra("state", STATE_UNPLUGGED); |
| + int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
| + String name = intent.getStringExtra("name"); |
| + Log.d(TAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": " |
| + + "a=" + intent.getAction() + ", s=" |
| + + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" |
| + + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb=" |
| + + isInitialStickyBroadcast()); |
| + hasWiredHeadset = (state == STATE_PLUGGED); |
| + updateAudioDeviceState(); |
| + } |
| + }; |
| + |
| + /** Construction. */ |
| + static AppRTCAudioManager create(Context context) { |
| + return new AppRTCAudioManager(context); |
| } |
| - private AppRTCAudioManager(Context context, Runnable deviceStateChangeListener) { |
| + private AppRTCAudioManager(Context context) { |
| + Log.d(TAG, "ctor"); |
| apprtcContext = context; |
| - onStateChangeListener = deviceStateChangeListener; |
| audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); |
| + bluetoothManager = AppRTCBluetoothManager.create(context, this); |
| + wiredHeadsetReceiver = new WiredHeadsetReceiver(); |
| + amState = AudioManagerState.UNINITIALIZED; |
| SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); |
| useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pref_speakerphone_key), |
| context.getString(R.string.pref_speakerphone_default)); |
| - |
| + Log.d(TAG, "useSpeakerphone: " + useSpeakerphone); |
| if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { |
| defaultAudioDevice = AudioDevice.EARPIECE; |
| } else { |
| @@ -132,19 +185,28 @@ public class AppRTCAudioManager { |
| onProximitySensorChangedState(); |
| } |
| }); |
| + |
| + Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice); |
| AppRTCUtils.logDeviceInfo(TAG); |
| } |
| - public void init() { |
| - Log.d(TAG, "init"); |
| - if (initialized) { |
| + public void start(AudioManagerEvents audioManagerEvents) { |
| + Log.d(TAG, "start"); |
| + if (amState == AudioManagerState.RUNNING) { |
| + Log.e(TAG, "AudioManager is already active"); |
| return; |
| } |
| + // TODO(henrika): perhaps call new method called preInitAudio() here if UNINITIALIZED. |
| - // Store current audio state so we can restore it when close() is called. |
| + Log.d(TAG, "AudioManager starts..."); |
| + this.audioManagerEvents = audioManagerEvents; |
| + amState = AudioManagerState.RUNNING; |
| + |
| + // Store current audio state so we can restore it when stop() is called. |
| savedAudioMode = audioManager.getMode(); |
| savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn(); |
| savedIsMicrophoneMute = audioManager.isMicrophoneMute(); |
| + hasWiredHeadset = hasWiredHeadset(); |
| // Create an AudioManager.OnAudioFocusChangeListener instance. |
| audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() { |
| @@ -199,31 +261,42 @@ public class AppRTCAudioManager { |
| // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is |
| // required to be in this mode when playout and/or recording starts for |
| // best possible VoIP performance. |
| - // TODO(henrika): we migh want to start with RINGTONE mode here instead. |
| audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); |
| // Always disable microphone mute during a WebRTC call. |
| setMicrophoneMute(false); |
| + // Set initial device states. |
| + userSelectedAudioDevice = AudioDevice.NONE; |
| + selectedAudioDevice = AudioDevice.NONE; |
| + audioDevices.clear(); |
| + |
| + // Initialize and start Bluetooth if a BT device is available or initiate |
| + // detection of new (enabled) BT devices. |
| + bluetoothManager.start(); |
| + |
| // Do initial selection of audio device. This setting can later be changed |
| - // either by adding/removing a wired headset or by covering/uncovering the |
| - // proximity sensor. |
| - updateAudioDeviceState(hasWiredHeadset()); |
| + // either by adding/removing a BT or wired headset or by covering/uncovering |
| + // the proximity sensor. |
| + updateAudioDeviceState(); |
| // Register receiver for broadcast intents related to adding/removing a |
| - // wired headset (Intent.ACTION_HEADSET_PLUG). |
| - registerForWiredHeadsetIntentBroadcast(); |
| - |
| - initialized = true; |
| + // wired headset. |
| + registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG)); |
| + Log.d(TAG, "AudioManager started"); |
| } |
| - public void close() { |
| - Log.d(TAG, "close"); |
| - if (!initialized) { |
| + public void stop() { |
| + Log.d(TAG, "stop"); |
| + if (amState != AudioManagerState.RUNNING) { |
| + Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState); |
| return; |
| } |
| + amState = AudioManagerState.UNINITIALIZED; |
| - unregisterForWiredHeadsetIntentBroadcast(); |
| + unregisterReceiver(wiredHeadsetReceiver); |
| + |
| + bluetoothManager.stop(); |
| // Restore previously stored audio states. |
| setSpeakerphoneOn(savedIsSpeakerPhoneOn); |
| @@ -240,94 +313,86 @@ public class AppRTCAudioManager { |
| proximitySensor = null; |
| } |
| - initialized = false; |
| + audioManagerEvents = null; |
| + Log.d(TAG, "AudioManager stopped"); |
| } |
| /** Changes selection of the currently active audio device. */ |
| - public void setAudioDevice(AudioDevice device) { |
| - Log.d(TAG, "setAudioDevice(device=" + device + ")"); |
| + private void setAudioDeviceInternal(AudioDevice device) { |
| + Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")"); |
| AppRTCUtils.assertIsTrue(audioDevices.contains(device)); |
| switch (device) { |
| case SPEAKER_PHONE: |
| setSpeakerphoneOn(true); |
| - selectedAudioDevice = AudioDevice.SPEAKER_PHONE; |
| break; |
| case EARPIECE: |
| setSpeakerphoneOn(false); |
| - selectedAudioDevice = AudioDevice.EARPIECE; |
| break; |
| case WIRED_HEADSET: |
| setSpeakerphoneOn(false); |
| - selectedAudioDevice = AudioDevice.WIRED_HEADSET; |
| + break; |
| + case BLUETOOTH: |
| + setSpeakerphoneOn(false); |
| break; |
| default: |
| Log.e(TAG, "Invalid audio device selection"); |
| break; |
| } |
| - onAudioManagerChangedState(); |
| + selectedAudioDevice = device; |
| + } |
| + |
| + /** |
| + * Changes default audio device. |
| + * TODO(henrika): add usage of this method in the AppRTCMobile client. |
| + */ |
| + public synchronized void setDefaultAudioDevice(AudioDevice defaultDevice) { |
| + switch (defaultDevice) { |
| + case SPEAKER_PHONE: |
| + defaultAudioDevice = defaultDevice; |
| + break; |
| + case EARPIECE: |
| + if (hasEarpiece()) { |
| + defaultAudioDevice = defaultDevice; |
| + } else { |
| + defaultAudioDevice = AudioDevice.SPEAKER_PHONE; |
| + } |
| + break; |
| + default: |
| + Log.e(TAG, "Invalid default audio device selection"); |
| + break; |
| + } |
| + Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")"); |
| + updateAudioDeviceState(); |
| + } |
| + |
| + /** Changes selection of the currently active audio device. */ |
| + public synchronized void selectAudioDevice(AudioDevice device) { |
| + if (!audioDevices.contains(device)) { |
| + Log.e(TAG, "Can not select " + device + " from available " + audioDevices); |
| + } |
| + userSelectedAudioDevice = device; |
| + updateAudioDeviceState(); |
| } |
| /** Returns current set of available/selectable audio devices. */ |
| - public Set<AudioDevice> getAudioDevices() { |
| + public synchronized Set<AudioDevice> getAudioDevices() { |
|
magjed_webrtc
2016/12/10 19:34:44
These functions that you have marked synchronized,
henrika_webrtc
2016/12/12 14:13:25
Done.
|
| return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices)); |
| } |
| /** Returns the currently selected audio device. */ |
| - public AudioDevice getSelectedAudioDevice() { |
| + public synchronized AudioDevice getSelectedAudioDevice() { |
| return selectedAudioDevice; |
| } |
| - /** |
| - * Registers receiver for the broadcasted intent when a wired headset is |
| - * plugged in or unplugged. The received intent will have an extra |
| - * 'state' value where 0 means unplugged, and 1 means plugged. |
| - */ |
| - private void registerForWiredHeadsetIntentBroadcast() { |
| - IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); |
| - |
| - /** Receiver which handles changes in wired headset availability. */ |
| - wiredHeadsetReceiver = new BroadcastReceiver() { |
| - private static final int STATE_UNPLUGGED = 0; |
| - private static final int STATE_PLUGGED = 1; |
| - private static final int HAS_NO_MIC = 0; |
| - private static final int HAS_MIC = 1; |
| - |
| - @Override |
| - public void onReceive(Context context, Intent intent) { |
| - int state = intent.getIntExtra("state", STATE_UNPLUGGED); |
| - int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); |
| - String name = intent.getStringExtra("name"); |
| - Log.d(TAG, "BroadcastReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": " |
| - + "a=" + intent.getAction() + ", s=" |
| - + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m=" |
| - + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb=" |
| - + isInitialStickyBroadcast()); |
| - |
| - boolean hasWiredHeadset = (state == STATE_PLUGGED); |
| - switch (state) { |
| - case STATE_UNPLUGGED: |
| - updateAudioDeviceState(hasWiredHeadset); |
| - break; |
| - case STATE_PLUGGED: |
| - if (selectedAudioDevice != AudioDevice.WIRED_HEADSET) { |
| - updateAudioDeviceState(hasWiredHeadset); |
| - } |
| - break; |
| - default: |
| - Log.e(TAG, "Invalid state"); |
| - break; |
| - } |
| - } |
| - }; |
| - |
| - apprtcContext.registerReceiver(wiredHeadsetReceiver, filter); |
| + /** Helper method for receiver registration. */ |
| + private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { |
| + apprtcContext.registerReceiver(receiver, filter); |
| } |
| - /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */ |
| - private void unregisterForWiredHeadsetIntentBroadcast() { |
| - apprtcContext.unregisterReceiver(wiredHeadsetReceiver); |
| - wiredHeadsetReceiver = null; |
| + /** Helper method for unregistration of an existing receiver. */ |
| + private void unregisterReceiver(BroadcastReceiver receiver) { |
| + apprtcContext.unregisterReceiver(receiver); |
| } |
| /** Sets the speaker phone mode. */ |
| @@ -365,55 +430,141 @@ public class AppRTCAudioManager { |
| return audioManager.isWiredHeadsetOn(); |
| } |
| - /** Update list of possible audio devices and make new device selection. */ |
| - private void updateAudioDeviceState(boolean hasWiredHeadset) { |
| - // Update the list of available audio devices. |
| - audioDevices.clear(); |
| + /** Updates list of possible audio devices and make new device selection. */ |
| + public void updateAudioDeviceState() { |
|
magjed_webrtc
2016/12/10 19:34:44
It would be nice with tests for this function as w
henrika_webrtc
2016/12/12 14:13:25
I got the same idea when writing the other test. A
|
| + threadChecker.checkIsOnValidThread(); |
| + Log.d(TAG, "--- updateAudioDeviceState: " |
| + + "wired headset=" + hasWiredHeadset + ", " |
| + + "BT state=" + bluetoothManager.getState()); |
| + Log.d(TAG, "Device status: " |
| + + "available=" + audioDevices + ", " |
| + + "selected=" + selectedAudioDevice + ", " |
| + + "user selected=" + userSelectedAudioDevice); |
| + |
| + // Check if any Bluetooth headset is connected. The internal BT state will |
| + // change accordingly. |
| + // TODO(henrika): perhaps wrap required state into BT manager. |
| + if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE |
| + || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE |
| + || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_DISCONNECTING) { |
| + bluetoothManager.updateDevice(); |
| + } |
| + |
| + // Update the set of available audio devices. |
| + Set<AudioDevice> newAudioDevices = new HashSet<>(); |
| + |
| + if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED |
| + || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING |
| + || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) { |
| + newAudioDevices.add(AudioDevice.BLUETOOTH); |
| + } |
| + |
| if (hasWiredHeadset) { |
| // If a wired headset is connected, then it is the only possible option. |
| - audioDevices.add(AudioDevice.WIRED_HEADSET); |
| + 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). |
| - audioDevices.add(AudioDevice.SPEAKER_PHONE); |
| + newAudioDevices.add(AudioDevice.SPEAKER_PHONE); |
| if (hasEarpiece()) { |
| - audioDevices.add(AudioDevice.EARPIECE); |
| + newAudioDevices.add(AudioDevice.EARPIECE); |
| } |
| } |
| - Log.d(TAG, "audioDevices: " + audioDevices); |
| - // Switch to correct audio device given the list of available audio devices. |
| - if (hasWiredHeadset) { |
| - setAudioDevice(AudioDevice.WIRED_HEADSET); |
| - } else { |
| - setAudioDevice(defaultAudioDevice); |
| + // Update the existing audio device set if needed. |
| + boolean audioDeviceSetUpdated = |
|
magjed_webrtc
2016/12/10 19:34:44
Is it possible to write it like this instead:
bool
henrika_webrtc
2016/12/12 14:13:25
Yep, it works ;-)
|
| + !audioDevices.containsAll(newAudioDevices) || !newAudioDevices.containsAll(audioDevices); |
| + if (audioDeviceSetUpdated) { |
| + audioDevices.clear(); |
| + audioDevices.addAll(newAudioDevices); |
| } |
| - } |
| - /** Called each time a new audio device has been added or removed. */ |
| - private void onAudioManagerChangedState() { |
| - Log.d(TAG, "onAudioManagerChangedState: devices=" + audioDevices + ", selected=" |
| - + selectedAudioDevice); |
| - |
| - // Enable the proximity sensor if there are two available audio devices |
| - // in the list. Given the current implementation, we know that the choice |
| - // will then be between EARPIECE and SPEAKER_PHONE. |
| - if (audioDevices.size() == 2) { |
| - AppRTCUtils.assertIsTrue(audioDevices.contains(AudioDevice.EARPIECE) |
| - && audioDevices.contains(AudioDevice.SPEAKER_PHONE)); |
| - // Start the proximity sensor. |
| - proximitySensor.start(); |
| - } else if (audioDevices.size() == 1) { |
| - // Stop the proximity sensor since it is no longer needed. |
| - proximitySensor.stop(); |
| - } else { |
| - Log.e(TAG, "Invalid device list"); |
| + // 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); |
| + |
| + // 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()); |
| } |
| - if (onStateChangeListener != null) { |
| - // Run callback to notify a listening client. The client can then |
| - // use public getters to query the new state. |
| - onStateChangeListener.run(); |
| + // Start or stop Bluetooth SCO connection given states set earlier. |
| + if (needBluetoothAudioStop) { |
| + bluetoothManager.stopScoAudio(); |
| + bluetoothManager.updateDevice(); |
| + } |
| + |
| + if (needBluetoothAudioStart && !needBluetoothAudioStop) { |
| + // 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; |
| + } |
| + } |
| + |
| + // Update selected audio device. |
| + AudioDevice newAudioDevice = selectedAudioDevice; |
| + |
| + 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 { |
| + // 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 selection. |
| + newAudioDevice = defaultAudioDevice; |
| + } |
| + // 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: " |
| + + "available=" + audioDevices + ", " |
| + + "selected=" + newAudioDevice); |
| + if (audioManagerEvents != null) { |
| + // Notify a listening client that audio device has been changed. |
| + audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices); |
| + } |
| } |
| + Log.d(TAG, "--- updateAudioDeviceState done"); |
| } |
| } |