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. onAudioDeviceChanged( AudioDevice selectedAudioDevice, Set<AudioDevice> availableAudioDevices)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 */ onProximitySensorChangedState()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 onReceive(Context context, Intent intent)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. */ create(Context context)160 static AppRTCAudioManager create(Context context) { 161 return new AppRTCAudioManager(context); 162 } 163 AppRTCAudioManager(Context context)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. start(AudioManagerEvents audioManagerEvents)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. stop()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. */ setAudioDeviceInternal(AudioDevice 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 */ setDefaultAudioDevice(AudioDevice defaultDevice)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. */ selectAudioDevice(AudioDevice 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. */ getAudioDevices()388 public Set<AudioDevice> getAudioDevices() { 389 ThreadUtils.checkIsOnMainThread(); 390 return Collections.unmodifiableSet(new HashSet<>(audioDevices)); 391 } 392 393 /** Returns the currently selected audio device. */ getSelectedAudioDevice()394 public AudioDevice getSelectedAudioDevice() { 395 ThreadUtils.checkIsOnMainThread(); 396 return selectedAudioDevice; 397 } 398 399 /** Helper method for receiver registration. */ registerReceiver(BroadcastReceiver receiver, IntentFilter filter)400 private void registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { 401 apprtcContext.registerReceiver(receiver, filter); 402 } 403 404 /** Helper method for unregistration of an existing receiver. */ unregisterReceiver(BroadcastReceiver receiver)405 private void unregisterReceiver(BroadcastReceiver receiver) { 406 apprtcContext.unregisterReceiver(receiver); 407 } 408 409 /** Sets the speaker phone mode. */ setSpeakerphoneOn(boolean on)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. */ setMicrophoneMute(boolean on)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. */ hasEarpiece()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 hasWiredHeadset()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 */ updateAudioDeviceState()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 } 595