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