MagicAudioManager.java 23 KB

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