AppRTCAudioManager.java

  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}