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