MagicAudioManager.java 27 KB

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