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