MagicAudioManager.java 26 KB

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