tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AppRTCAudioManager.java (23962B)


      1 /*
      2 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
      3 *
      4 *  Use of this source code is governed by a BSD-style license
      5 *  that can be found in the LICENSE file in the root of the source
      6 *  tree. An additional intellectual property rights grant can be found
      7 *  in the file PATENTS.  All contributing project authors may
      8 *  be found in the AUTHORS file in the root of the source tree.
      9 */
     10 
     11 package org.appspot.apprtc;
     12 
     13 import android.content.BroadcastReceiver;
     14 import android.content.Context;
     15 import android.content.Intent;
     16 import android.content.IntentFilter;
     17 import android.content.SharedPreferences;
     18 import android.content.pm.PackageManager;
     19 import android.media.AudioDeviceInfo;
     20 import android.media.AudioManager;
     21 import android.os.Build;
     22 import android.preference.PreferenceManager;
     23 import android.util.Log;
     24 import androidx.annotation.Nullable;
     25 import java.util.Collections;
     26 import java.util.HashSet;
     27 import java.util.Set;
     28 import org.appspot.apprtc.util.AppRTCUtils;
     29 import org.webrtc.ThreadUtils;
     30 
     31 /**
     32 * AppRTCAudioManager manages all audio related parts of the AppRTC demo.
     33 */
     34 public class AppRTCAudioManager {
     35  private static final String TAG = "AppRTCAudioManager";
     36  private static final String SPEAKERPHONE_AUTO = "auto";
     37  private static final String SPEAKERPHONE_TRUE = "true";
     38  private static final String SPEAKERPHONE_FALSE = "false";
     39 
     40  /**
     41   * AudioDevice is the names of possible audio devices that we currently
     42   * support.
     43   */
     44  public enum AudioDevice { SPEAKER_PHONE, WIRED_HEADSET, EARPIECE, BLUETOOTH, NONE }
     45 
     46  /** AudioManager state. */
     47  public enum AudioManagerState {
     48    UNINITIALIZED,
     49    PREINITIALIZED,
     50    RUNNING,
     51  }
     52 
     53  /** Selected audio device change event. */
     54  public interface AudioManagerEvents {
     55    // Callback fired once audio device is changed or list of available audio devices changed.
     56    void onAudioDeviceChanged(
     57        AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices);
     58  }
     59 
     60  private final Context apprtcContext;
     61  @Nullable
     62  private AudioManager audioManager;
     63 
     64  @Nullable
     65  private AudioManagerEvents audioManagerEvents;
     66  private AudioManagerState amState;
     67  private int savedAudioMode = AudioManager.MODE_INVALID;
     68  private boolean savedIsSpeakerPhoneOn;
     69  private boolean savedIsMicrophoneMute;
     70  private boolean hasWiredHeadset;
     71 
     72  // Default audio device; speaker phone for video calls or earpiece for audio
     73  // only calls.
     74  private AudioDevice defaultAudioDevice;
     75 
     76  // Contains the currently selected audio device.
     77  // This device is changed automatically using a certain scheme where e.g.
     78  // a wired headset "wins" over speaker phone. It is also possible for a
     79  // user to explicitly select a device (and overrid any predefined scheme).
     80  // See `userSelectedAudioDevice` for details.
     81  private AudioDevice selectedAudioDevice;
     82 
     83  // Contains the user-selected audio device which overrides the predefined
     84  // selection scheme.
     85  // TODO(henrika): always set to AudioDevice.NONE today. Add support for
     86  // explicit selection based on choice by userSelectedAudioDevice.
     87  private AudioDevice userSelectedAudioDevice;
     88 
     89  // Contains speakerphone setting: auto, true or false
     90  @Nullable private final String useSpeakerphone;
     91 
     92  // Proximity sensor object. It measures the proximity of an object in cm
     93  // relative to the view screen of a device and can therefore be used to
     94  // assist device switching (close to ear <=> use headset earpiece if
     95  // available, far from ear <=> use speaker phone).
     96  @Nullable private AppRTCProximitySensor proximitySensor;
     97 
     98  // Handles all tasks related to Bluetooth headset devices.
     99  private final AppRTCBluetoothManager bluetoothManager;
    100 
    101  // Contains a list of available audio devices. A Set collection is used to
    102  // avoid duplicate elements.
    103  private Set<AudioDevice> audioDevices = new HashSet<>();
    104 
    105  // Broadcast receiver for wired headset intent broadcasts.
    106  private BroadcastReceiver wiredHeadsetReceiver;
    107 
    108  // Callback method for changes in audio focus.
    109  @Nullable
    110  private AudioManager.OnAudioFocusChangeListener audioFocusChangeListener;
    111 
    112  /**
    113   * This method is called when the proximity sensor reports a state change,
    114   * e.g. from "NEAR to FAR" or from "FAR to NEAR".
    115   */
    116  private void onProximitySensorChangedState() {
    117    if (!useSpeakerphone.equals(SPEAKERPHONE_AUTO)) {
    118      return;
    119    }
    120 
    121    // The proximity sensor should only be activated when there are exactly two
    122    // available audio devices.
    123    if (audioDevices.size() == 2 && audioDevices.contains(AppRTCAudioManager.AudioDevice.EARPIECE)
    124        && audioDevices.contains(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE)) {
    125      if (proximitySensor.sensorReportsNearState()) {
    126        // Sensor reports that a "handset is being held up to a person's ear",
    127        // or "something is covering the light sensor".
    128        setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.EARPIECE);
    129      } else {
    130        // Sensor reports that a "handset is removed from a person's ear", or
    131        // "the light sensor is no longer covered".
    132        setAudioDeviceInternal(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
    133      }
    134    }
    135  }
    136 
    137  /* Receiver which handles changes in wired headset availability. */
    138  private class WiredHeadsetReceiver extends BroadcastReceiver {
    139    private static final int STATE_UNPLUGGED = 0;
    140    private static final int STATE_PLUGGED = 1;
    141    private static final int HAS_NO_MIC = 0;
    142    private static final int HAS_MIC = 1;
    143 
    144    @Override
    145    public void onReceive(Context context, Intent intent) {
    146      int state = intent.getIntExtra("state", STATE_UNPLUGGED);
    147      int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
    148      String name = intent.getStringExtra("name");
    149      Log.d(TAG, "WiredHeadsetReceiver.onReceive" + AppRTCUtils.getThreadInfo() + ": "
    150              + "a=" + intent.getAction() + ", s="
    151              + (state == STATE_UNPLUGGED ? "unplugged" : "plugged") + ", m="
    152              + (microphone == HAS_MIC ? "mic" : "no mic") + ", n=" + name + ", sb="
    153              + isInitialStickyBroadcast());
    154      hasWiredHeadset = (state == STATE_PLUGGED);
    155      updateAudioDeviceState();
    156    }
    157  }
    158 
    159  /** Construction. */
    160  static AppRTCAudioManager create(Context context) {
    161    return new AppRTCAudioManager(context);
    162  }
    163 
    164  private AppRTCAudioManager(Context context) {
    165    Log.d(TAG, "ctor");
    166    ThreadUtils.checkIsOnMainThread();
    167    apprtcContext = context;
    168    audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
    169    bluetoothManager = AppRTCBluetoothManager.create(context, this);
    170    wiredHeadsetReceiver = new WiredHeadsetReceiver();
    171    amState = AudioManagerState.UNINITIALIZED;
    172 
    173    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    174    useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pref_speakerphone_key),
    175        context.getString(R.string.pref_speakerphone_default));
    176    Log.d(TAG, "useSpeakerphone: " + useSpeakerphone);
    177    if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) {
    178      defaultAudioDevice = AudioDevice.EARPIECE;
    179    } else {
    180      defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
    181    }
    182 
    183    // Create and initialize the proximity sensor.
    184    // Tablet devices (e.g. Nexus 7) does not support proximity sensors.
    185    // Note that, the sensor will not be active until start() has been called.
    186    proximitySensor = AppRTCProximitySensor.create(context,
    187        // This method will be called each time a state change is detected.
    188        // Example: user holds their hand over the device (closer than ~5 cm),
    189        // or removes their hand from the device.
    190        this ::onProximitySensorChangedState);
    191 
    192    Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice);
    193    AppRTCUtils.logDeviceInfo(TAG);
    194  }
    195 
    196  @SuppressWarnings("deprecation") // TODO(henrika): audioManager.requestAudioFocus() is deprecated.
    197  public void start(AudioManagerEvents audioManagerEvents) {
    198    Log.d(TAG, "start");
    199    ThreadUtils.checkIsOnMainThread();
    200    if (amState == AudioManagerState.RUNNING) {
    201      Log.e(TAG, "AudioManager is already active");
    202      return;
    203    }
    204    // TODO(henrika): perhaps call new method called preInitAudio() here if UNINITIALIZED.
    205 
    206    Log.d(TAG, "AudioManager starts...");
    207    this.audioManagerEvents = audioManagerEvents;
    208    amState = AudioManagerState.RUNNING;
    209 
    210    // Store current audio state so we can restore it when stop() is called.
    211    savedAudioMode = audioManager.getMode();
    212    savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
    213    savedIsMicrophoneMute = audioManager.isMicrophoneMute();
    214    hasWiredHeadset = hasWiredHeadset();
    215 
    216    // Create an AudioManager.OnAudioFocusChangeListener instance.
    217    audioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
    218      // Called on the listener to notify if the audio focus for this listener has been changed.
    219      // The `focusChange` value indicates whether the focus was gained, whether the focus was lost,
    220      // and whether that loss is transient, or whether the new focus holder will hold it for an
    221      // unknown amount of time.
    222      // TODO(henrika): possibly extend support of handling audio-focus changes. Only contains
    223      // logging for now.
    224      @Override
    225      public void onAudioFocusChange(int focusChange) {
    226        final String typeOfChange;
    227        switch (focusChange) {
    228          case AudioManager.AUDIOFOCUS_GAIN:
    229            typeOfChange = "AUDIOFOCUS_GAIN";
    230            break;
    231          case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
    232            typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT";
    233            break;
    234          case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
    235            typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
    236            break;
    237          case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
    238            typeOfChange = "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
    239            break;
    240          case AudioManager.AUDIOFOCUS_LOSS:
    241            typeOfChange = "AUDIOFOCUS_LOSS";
    242            break;
    243          case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
    244            typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT";
    245            break;
    246          case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
    247            typeOfChange = "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
    248            break;
    249          default:
    250            typeOfChange = "AUDIOFOCUS_INVALID";
    251            break;
    252        }
    253        Log.d(TAG, "onAudioFocusChange: " + typeOfChange);
    254      }
    255    };
    256 
    257    // Request audio playout focus (without ducking) and install listener for changes in focus.
    258    int result = audioManager.requestAudioFocus(audioFocusChangeListener,
    259        AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
    260    if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    261      Log.d(TAG, "Audio focus request granted for VOICE_CALL streams");
    262    } else {
    263      Log.e(TAG, "Audio focus request failed");
    264    }
    265 
    266    // Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
    267    // required to be in this mode when playout and/or recording starts for
    268    // best possible VoIP performance.
    269    audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
    270 
    271    // Always disable microphone mute during a WebRTC call.
    272    setMicrophoneMute(false);
    273 
    274    // Set initial device states.
    275    userSelectedAudioDevice = AudioDevice.NONE;
    276    selectedAudioDevice = AudioDevice.NONE;
    277    audioDevices.clear();
    278 
    279    // Initialize and start Bluetooth if a BT device is available or initiate
    280    // detection of new (enabled) BT devices.
    281    bluetoothManager.start();
    282 
    283    // Do initial selection of audio device. This setting can later be changed
    284    // either by adding/removing a BT or wired headset or by covering/uncovering
    285    // the proximity sensor.
    286    updateAudioDeviceState();
    287 
    288    // Register receiver for broadcast intents related to adding/removing a
    289    // wired headset.
    290    registerReceiver(wiredHeadsetReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
    291    Log.d(TAG, "AudioManager started");
    292  }
    293 
    294  @SuppressWarnings("deprecation") // TODO(henrika): audioManager.abandonAudioFocus() is deprecated.
    295  public void stop() {
    296    Log.d(TAG, "stop");
    297    ThreadUtils.checkIsOnMainThread();
    298    if (amState != AudioManagerState.RUNNING) {
    299      Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState);
    300      return;
    301    }
    302    amState = AudioManagerState.UNINITIALIZED;
    303 
    304    unregisterReceiver(wiredHeadsetReceiver);
    305 
    306    bluetoothManager.stop();
    307 
    308    // Restore previously stored audio states.
    309    setSpeakerphoneOn(savedIsSpeakerPhoneOn);
    310    setMicrophoneMute(savedIsMicrophoneMute);
    311    audioManager.setMode(savedAudioMode);
    312 
    313    // Abandon audio focus. Gives the previous focus owner, if any, focus.
    314    audioManager.abandonAudioFocus(audioFocusChangeListener);
    315    audioFocusChangeListener = null;
    316    Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams");
    317 
    318    if (proximitySensor != null) {
    319      proximitySensor.stop();
    320      proximitySensor = null;
    321    }
    322 
    323    audioManagerEvents = null;
    324    Log.d(TAG, "AudioManager stopped");
    325  }
    326 
    327  /** Changes selection of the currently active audio device. */
    328  private void setAudioDeviceInternal(AudioDevice device) {
    329    Log.d(TAG, "setAudioDeviceInternal(device=" + device + ")");
    330    AppRTCUtils.assertIsTrue(audioDevices.contains(device));
    331 
    332    switch (device) {
    333      case SPEAKER_PHONE:
    334        setSpeakerphoneOn(true);
    335        break;
    336      case EARPIECE:
    337        setSpeakerphoneOn(false);
    338        break;
    339      case WIRED_HEADSET:
    340        setSpeakerphoneOn(false);
    341        break;
    342      case BLUETOOTH:
    343        setSpeakerphoneOn(false);
    344        break;
    345      default:
    346        Log.e(TAG, "Invalid audio device selection");
    347        break;
    348    }
    349    selectedAudioDevice = device;
    350  }
    351 
    352  /**
    353   * Changes default audio device.
    354   * TODO(henrika): add usage of this method in the AppRTCMobile client.
    355   */
    356  public void setDefaultAudioDevice(AudioDevice defaultDevice) {
    357    ThreadUtils.checkIsOnMainThread();
    358    switch (defaultDevice) {
    359      case SPEAKER_PHONE:
    360        defaultAudioDevice = defaultDevice;
    361        break;
    362      case EARPIECE:
    363        if (hasEarpiece()) {
    364          defaultAudioDevice = defaultDevice;
    365        } else {
    366          defaultAudioDevice = AudioDevice.SPEAKER_PHONE;
    367        }
    368        break;
    369      default:
    370        Log.e(TAG, "Invalid default audio device selection");
    371        break;
    372    }
    373    Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")");
    374    updateAudioDeviceState();
    375  }
    376 
    377  /** Changes selection of the currently active audio device. */
    378  public void selectAudioDevice(AudioDevice device) {
    379    ThreadUtils.checkIsOnMainThread();
    380    if (!audioDevices.contains(device)) {
    381      Log.e(TAG, "Can not select " + device + " from available " + audioDevices);
    382    }
    383    userSelectedAudioDevice = device;
    384    updateAudioDeviceState();
    385  }
    386 
    387  /** Returns current set of available/selectable audio devices. */
    388  public Set<AudioDevice> getAudioDevices() {
    389    ThreadUtils.checkIsOnMainThread();
    390    return Collections.unmodifiableSet(new HashSet<>(audioDevices));
    391  }
    392 
    393  /** Returns the currently selected audio device. */
    394  public AudioDevice getSelectedAudioDevice() {
    395    ThreadUtils.checkIsOnMainThread();
    396    return selectedAudioDevice;
    397  }
    398 
    399  /** Helper method for receiver registration. */
    400  private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
    401    apprtcContext.registerReceiver(receiver, filter);
    402  }
    403 
    404  /** Helper method for unregistration of an existing receiver. */
    405  private void unregisterReceiver(BroadcastReceiver receiver) {
    406    apprtcContext.unregisterReceiver(receiver);
    407  }
    408 
    409  /** Sets the speaker phone mode. */
    410  private void setSpeakerphoneOn(boolean on) {
    411    boolean wasOn = audioManager.isSpeakerphoneOn();
    412    if (wasOn == on) {
    413      return;
    414    }
    415    audioManager.setSpeakerphoneOn(on);
    416  }
    417 
    418  /** Sets the microphone mute state. */
    419  private void setMicrophoneMute(boolean on) {
    420    boolean wasMuted = audioManager.isMicrophoneMute();
    421    if (wasMuted == on) {
    422      return;
    423    }
    424    audioManager.setMicrophoneMute(on);
    425  }
    426 
    427  /** Gets the current earpiece state. */
    428  private boolean hasEarpiece() {
    429    return apprtcContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
    430  }
    431 
    432  /**
    433   * Checks whether a wired headset is connected or not.
    434   * This is not a valid indication that audio playback is actually over
    435   * the wired headset as audio routing depends on other conditions. We
    436   * only use it as an early indicator (during initialization) of an attached
    437   * wired headset.
    438   */
    439  @Deprecated
    440  private boolean hasWiredHeadset() {
    441    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
    442      return audioManager.isWiredHeadsetOn();
    443    } else {
    444      final AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_ALL);
    445      for (AudioDeviceInfo device : devices) {
    446        final int type = device.getType();
    447        if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
    448          Log.d(TAG, "hasWiredHeadset: found wired headset");
    449          return true;
    450        } else if (type == AudioDeviceInfo.TYPE_USB_DEVICE) {
    451          Log.d(TAG, "hasWiredHeadset: found USB audio device");
    452          return true;
    453        }
    454      }
    455      return false;
    456    }
    457  }
    458 
    459  /**
    460   * Updates list of possible audio devices and make new device selection.
    461   * TODO(henrika): add unit test to verify all state transitions.
    462   */
    463  public void updateAudioDeviceState() {
    464    ThreadUtils.checkIsOnMainThread();
    465    Log.d(TAG, "--- updateAudioDeviceState: "
    466            + "wired headset=" + hasWiredHeadset + ", "
    467            + "BT state=" + bluetoothManager.getState());
    468    Log.d(TAG, "Device status: "
    469            + "available=" + audioDevices + ", "
    470            + "selected=" + selectedAudioDevice + ", "
    471            + "user selected=" + userSelectedAudioDevice);
    472 
    473    // Check if any Bluetooth headset is connected. The internal BT state will
    474    // change accordingly.
    475    // TODO(henrika): perhaps wrap required state into BT manager.
    476    if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
    477        || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE
    478        || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_DISCONNECTING) {
    479      bluetoothManager.updateDevice();
    480    }
    481 
    482    // Update the set of available audio devices.
    483    Set<AudioDevice> newAudioDevices = new HashSet<>();
    484 
    485    if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
    486        || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
    487        || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) {
    488      newAudioDevices.add(AudioDevice.BLUETOOTH);
    489    }
    490 
    491    if (hasWiredHeadset) {
    492      // If a wired headset is connected, then it is the only possible option.
    493      newAudioDevices.add(AudioDevice.WIRED_HEADSET);
    494    } else {
    495      // No wired headset, hence the audio-device list can contain speaker
    496      // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
    497      newAudioDevices.add(AudioDevice.SPEAKER_PHONE);
    498      if (hasEarpiece()) {
    499        newAudioDevices.add(AudioDevice.EARPIECE);
    500      }
    501    }
    502    // Store state which is set to true if the device list has changed.
    503    boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
    504    // Update the existing audio device set.
    505    audioDevices = newAudioDevices;
    506    // Correct user selected audio devices if needed.
    507    if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE
    508        && userSelectedAudioDevice == AudioDevice.BLUETOOTH) {
    509      // If BT is not available, it can't be the user selection.
    510      userSelectedAudioDevice = AudioDevice.NONE;
    511    }
    512    if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE) {
    513      // If user selected speaker phone, but then plugged wired headset then make
    514      // wired headset as user selected device.
    515      userSelectedAudioDevice = AudioDevice.WIRED_HEADSET;
    516    }
    517    if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET) {
    518      // If user selected wired headset, but then unplugged wired headset then make
    519      // speaker phone as user selected device.
    520      userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE;
    521    }
    522 
    523    // Need to start Bluetooth if it is available and user either selected it explicitly or
    524    // user did not select any output device.
    525    boolean needBluetoothAudioStart =
    526        bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
    527        && (userSelectedAudioDevice == AudioDevice.NONE
    528               || userSelectedAudioDevice == AudioDevice.BLUETOOTH);
    529 
    530    // Need to stop Bluetooth audio if user selected different device and
    531    // Bluetooth SCO connection is established or in the process.
    532    boolean needBluetoothAudioStop =
    533        (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED
    534            || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING)
    535        && (userSelectedAudioDevice != AudioDevice.NONE
    536               && userSelectedAudioDevice != AudioDevice.BLUETOOTH);
    537 
    538    if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE
    539        || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING
    540        || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) {
    541      Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
    542              + "stop=" + needBluetoothAudioStop + ", "
    543              + "BT state=" + bluetoothManager.getState());
    544    }
    545 
    546    // Start or stop Bluetooth SCO connection given states set earlier.
    547    if (needBluetoothAudioStop) {
    548      bluetoothManager.stopScoAudio();
    549      bluetoothManager.updateDevice();
    550    }
    551 
    552    if (needBluetoothAudioStart && !needBluetoothAudioStop) {
    553      // Attempt to start Bluetooth SCO audio (takes a few second to start).
    554      if (!bluetoothManager.startScoAudio()) {
    555        // Remove BLUETOOTH from list of available devices since SCO failed.
    556        audioDevices.remove(AudioDevice.BLUETOOTH);
    557        audioDeviceSetUpdated = true;
    558      }
    559    }
    560 
    561    // Update selected audio device.
    562    final AudioDevice newAudioDevice;
    563 
    564    if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) {
    565      // If a Bluetooth is connected, then it should be used as output audio
    566      // device. Note that it is not sufficient that a headset is available;
    567      // an active SCO channel must also be up and running.
    568      newAudioDevice = AudioDevice.BLUETOOTH;
    569    } else if (hasWiredHeadset) {
    570      // If a wired headset is connected, but Bluetooth is not, then wired headset is used as
    571      // audio device.
    572      newAudioDevice = AudioDevice.WIRED_HEADSET;
    573    } else {
    574      // No wired headset and no Bluetooth, hence the audio-device list can contain speaker
    575      // phone (on a tablet), or speaker phone and earpiece (on mobile phone).
    576      // `defaultAudioDevice` contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE
    577      // depending on the user's selection.
    578      newAudioDevice = defaultAudioDevice;
    579    }
    580    // Switch to new device but only if there has been any changes.
    581    if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) {
    582      // Do the required device switch.
    583      setAudioDeviceInternal(newAudioDevice);
    584      Log.d(TAG, "New device status: "
    585              + "available=" + audioDevices + ", "
    586              + "selected=" + newAudioDevice);
    587      if (audioManagerEvents != null) {
    588        // Notify a listening client that audio device has been changed.
    589        audioManagerEvents.onAudioDeviceChanged(selectedAudioDevice, audioDevices);
    590      }
    591    }
    592    Log.d(TAG, "--- updateAudioDeviceState done");
    593  }
    594 }