MagicAudioManager.java 23 KB

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