MagicAudioManager.java 27 KB

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