| 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..a57e84f8ca0aa81ed8ba6596ec3831e16667044d 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,48 @@ 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 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;
|
|
|
| - private final AudioDevice defaultAudioDevice;
|
| + // Default audio device; speaker phone for video calls or earpiece for audio
|
| + // only calls.
|
| + private AudioDevice defaultAudioDevice;
|
| +
|
| + // Contains the currently selected audio device.
|
| + // This device is changed automatically using a certain scheme where e.g.
|
| + // a wired headset "wins" over speaker phone. It is also possible for a
|
| + // user to explicitly select a device (and overrid any predefined scheme).
|
| + // See |userSelectedAudioDevice| for details.
|
| + private AudioDevice selectedAudioDevice;
|
| +
|
| + // Contains the user-selected audio device which overrides the predefined
|
| + // selection scheme.
|
| + // TODO(henrika): always set to AudioDevice.NONE today. Add support for
|
| + // explicit selection based on choice by userSelectedAudioDevice.
|
| + private AudioDevice userSelectedAudioDevice;
|
|
|
| // Contains speakerphone setting: auto, true or false
|
| private final String useSpeakerphone;
|
| @@ -65,12 +94,12 @@ 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.
|
| - private final Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();
|
| + private Set<AudioDevice> audioDevices = new HashSet<AudioDevice>();
|
|
|
| // Broadcast receiver for wired headset intent broadcasts.
|
| private BroadcastReceiver wiredHeadsetReceiver;
|
| @@ -78,8 +107,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 +123,55 @@ 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");
|
| + ThreadUtils.checkIsOnMainThread();
|
| 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 +189,29 @@ 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");
|
| + ThreadUtils.checkIsOnMainThread();
|
| + if (amState == AudioManagerState.RUNNING) {
|
| + Log.e(TAG, "AudioManager is already active");
|
| return;
|
| }
|
| + // TODO(henrika): perhaps call new method called preInitAudio() here if UNINITIALIZED.
|
| +
|
| + Log.d(TAG, "AudioManager starts...");
|
| + this.audioManagerEvents = audioManagerEvents;
|
| + amState = AudioManagerState.RUNNING;
|
|
|
| - // Store current audio state so we can restore it when close() is called.
|
| + // 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 +266,43 @@ 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");
|
| + ThreadUtils.checkIsOnMainThread();
|
| + if (amState != AudioManagerState.RUNNING) {
|
| + Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState);
|
| return;
|
| }
|
| + amState = AudioManagerState.UNINITIALIZED;
|
| +
|
| + unregisterReceiver(wiredHeadsetReceiver);
|
|
|
| - unregisterForWiredHeadsetIntentBroadcast();
|
| + bluetoothManager.stop();
|
|
|
| // Restore previously stored audio states.
|
| setSpeakerphoneOn(savedIsSpeakerPhoneOn);
|
| @@ -240,94 +319,90 @@ 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 void setDefaultAudioDevice(AudioDevice defaultDevice) {
|
| + ThreadUtils.checkIsOnMainThread();
|
| + 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 void selectAudioDevice(AudioDevice device) {
|
| + ThreadUtils.checkIsOnMainThread();
|
| + 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() {
|
| + ThreadUtils.checkIsOnMainThread();
|
| return Collections.unmodifiableSet(new HashSet<AudioDevice>(audioDevices));
|
| }
|
|
|
| /** Returns the currently selected audio device. */
|
| public AudioDevice getSelectedAudioDevice() {
|
| + ThreadUtils.checkIsOnMainThread();
|
| 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 +440,139 @@ 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.
|
| + * TODO(henrika): add unit test to verify all state transitions.
|
| + */
|
| + public void updateAudioDeviceState() {
|
| + ThreadUtils.checkIsOnMainThread();
|
| + 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);
|
| + // 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;
|
| + }
|
|
|
| - // Switch to correct audio device given the list of available audio devices.
|
| - if (hasWiredHeadset) {
|
| - setAudioDevice(AudioDevice.WIRED_HEADSET);
|
| - } else {
|
| - setAudioDevice(defaultAudioDevice);
|
| + // 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());
|
| }
|
| - }
|
|
|
| - /** 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");
|
| + // 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;
|
| + }
|
| }
|
|
|
| - if (onStateChangeListener != null) {
|
| - // Run callback to notify a listening client. The client can then
|
| - // use public getters to query the new state.
|
| - onStateChangeListener.run();
|
| + // 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");
|
| }
|
| }
|
|
|