1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.media;
7 import android.bluetooth.BluetoothAdapter;
8 import android.bluetooth.BluetoothManager;
9 import android.content.BroadcastReceiver;
10 import android.content.ContentResolver;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.content.IntentFilter;
14 import android.content.pm.PackageManager;
15 import android.database.ContentObserver;
16 import android.media.AudioFormat;
17 import android.media.AudioManager;
18 import android.media.AudioRecord;
19 import android.media.AudioTrack;
20 import android.media.audiofx.AcousticEchoCanceler;
21 import android.os.Build;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.os.Process;
25 import android.provider.Settings;
26 import android.util.Log;
28 import org.chromium.base.CalledByNative;
29 import org.chromium.base.JNINamespace;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.List;
35 @JNINamespace("media")
36 class AudioManagerAndroid {
37 private static final String TAG = "AudioManagerAndroid";
39 // Set to true to enable debug logs. Avoid in production builds.
40 // NOTE: always check in as false.
41 private static final boolean DEBUG = false;
43 private static boolean runningOnJellyBeanOrHigher() {
44 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
47 private static boolean runningOnJellyBeanMR1OrHigher() {
48 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
51 private static boolean runningOnJellyBeanMR2OrHigher() {
52 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
55 /** Simple container for device information. */
56 private static class AudioDeviceName {
57 private final int mId;
58 private final String mName;
60 private AudioDeviceName(int id, String name) {
65 @CalledByNative("AudioDeviceName")
66 private String id() { return String.valueOf(mId); }
68 @CalledByNative("AudioDeviceName")
69 private String name() { return mName; }
72 // Supported audio device types.
73 private static final int DEVICE_DEFAULT = -2;
74 private static final int DEVICE_INVALID = -1;
75 private static final int DEVICE_SPEAKERPHONE = 0;
76 private static final int DEVICE_WIRED_HEADSET = 1;
77 private static final int DEVICE_EARPIECE = 2;
78 private static final int DEVICE_BLUETOOTH_HEADSET = 3;
79 private static final int DEVICE_COUNT = 4;
81 // Maps audio device types to string values. This map must be in sync
82 // with the device types above.
83 // TODO(henrika): add support for proper detection of device names and
84 // localize the name strings by using resource strings.
85 // See http://crbug.com/333208 for details.
86 private static final String[] DEVICE_NAMES = new String[] {
88 "Wired headset", // With or without microphone.
89 "Headset earpiece", // Only available on mobile phones.
90 "Bluetooth headset", // Requires BLUETOOTH permission.
93 // List of valid device types.
94 private static final Integer[] VALID_DEVICES = new Integer[] {
98 DEVICE_BLUETOOTH_HEADSET,
101 // Bluetooth audio SCO states. Example of valid state sequence:
102 // SCO_INVALID -> SCO_TURNING_ON -> SCO_ON -> SCO_TURNING_OFF -> SCO_OFF.
103 private static final int STATE_BLUETOOTH_SCO_INVALID = -1;
104 private static final int STATE_BLUETOOTH_SCO_OFF = 0;
105 private static final int STATE_BLUETOOTH_SCO_ON = 1;
106 private static final int STATE_BLUETOOTH_SCO_TURNING_ON = 2;
107 private static final int STATE_BLUETOOTH_SCO_TURNING_OFF = 3;
109 // Use 44.1kHz as the default sampling rate.
110 private static final int DEFAULT_SAMPLING_RATE = 44100;
111 // Randomly picked up frame size which is close to return value on N4.
112 // Return this value when getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
114 private static final int DEFAULT_FRAME_PER_BUFFER = 256;
116 private final AudioManager mAudioManager;
117 private final Context mContext;
118 private final long mNativeAudioManagerAndroid;
120 // Enabled during initialization if BLUETOOTH permission is granted.
121 private boolean mHasBluetoothPermission = false;
123 private int mSavedAudioMode = AudioManager.MODE_INVALID;
125 // Stores the audio states related to Bluetooth SCO audio, where some
126 // states are needed to keep track of intermediate states while the SCO
127 // channel is enabled or disabled (switching state can take a few seconds).
128 private int mBluetoothScoState = STATE_BLUETOOTH_SCO_INVALID;
130 private boolean mIsInitialized = false;
131 private boolean mSavedIsSpeakerphoneOn;
132 private boolean mSavedIsMicrophoneMute;
134 // Id of the requested audio device. Can only be modified by
135 // call to setDevice().
136 private int mRequestedAudioDevice = DEVICE_INVALID;
138 // Lock to protect |mAudioDevices| and |mRequestedAudioDevice| which can
139 // be accessed from the main thread and the audio manager thread.
140 private final Object mLock = new Object();
142 // Contains a list of currently available audio devices.
143 private boolean[] mAudioDevices = new boolean[DEVICE_COUNT];
145 private final ContentResolver mContentResolver;
146 private SettingsObserver mSettingsObserver = null;
147 private HandlerThread mSettingsObserverThread = null;
148 private int mCurrentVolume;
150 // Broadcast receiver for wired headset intent broadcasts.
151 private BroadcastReceiver mWiredHeadsetReceiver;
153 // Broadcast receiver for Bluetooth headset intent broadcasts.
154 // Utilized to detect changes in Bluetooth headset availability.
155 private BroadcastReceiver mBluetoothHeadsetReceiver;
157 // Broadcast receiver for Bluetooth SCO broadcasts.
158 // Utilized to detect if BT SCO streaming is on or off.
159 private BroadcastReceiver mBluetoothScoReceiver;
163 private static AudioManagerAndroid createAudioManagerAndroid(
165 long nativeAudioManagerAndroid) {
166 return new AudioManagerAndroid(context, nativeAudioManagerAndroid);
169 private AudioManagerAndroid(Context context, long nativeAudioManagerAndroid) {
171 mNativeAudioManagerAndroid = nativeAudioManagerAndroid;
172 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
173 mContentResolver = mContext.getContentResolver();
177 * Saves the initial speakerphone and microphone state.
178 * Populates the list of available audio devices and registers receivers
179 * for broadcast intents related to wired headset and Bluetooth devices.
182 private void init() {
183 if (DEBUG) logd("init");
187 for (int i = 0; i < DEVICE_COUNT; ++i) {
188 mAudioDevices[i] = false;
191 // Initialize audio device list with things we know is always available.
193 mAudioDevices[DEVICE_EARPIECE] = true;
195 mAudioDevices[DEVICE_SPEAKERPHONE] = true;
197 // Register receivers for broadcast intents related to Bluetooth device
198 // and Bluetooth SCO notifications. Requires BLUETOOTH permission.
199 registerBluetoothIntentsIfNeeded();
201 // Register receiver for broadcast intents related to adding/
202 // removing a wired headset (Intent.ACTION_HEADSET_PLUG).
203 registerForWiredHeadsetIntentBroadcast();
205 // Start observer for volume changes.
206 // TODO(henrika): try-catch parts below are added as a test to see if
207 // it avoids the crash in init() reported in http://crbug.com/336600.
208 // Should be removed if possible when we understand the reason better.
210 mSettingsObserverThread = new HandlerThread("SettingsObserver");
211 mSettingsObserverThread.start();
212 mSettingsObserver = new SettingsObserver(
213 new Handler(mSettingsObserverThread.getLooper()));
214 } catch (Exception e) {
215 // It is fine to rely on code below here to detect failure by
216 // observing mSettingsObserver==null.
217 Log.wtf(TAG, "SettingsObserver exception: ", e);
220 mIsInitialized = true;
221 if (DEBUG) reportUpdate();
225 * Unregister all previously registered intent receivers and restore
226 * the stored state (stored in {@link #init()}).
229 private void close() {
230 if (DEBUG) logd("close");
234 if (mSettingsObserverThread != null) {
235 mSettingsObserverThread.quit();
237 mSettingsObserverThread.join();
238 } catch (Exception e) {
239 Log.wtf(TAG, "HandlerThread.join() exception: ", e);
241 mSettingsObserverThread = null;
243 if (mContentResolver != null) {
244 mContentResolver.unregisterContentObserver(mSettingsObserver);
245 mSettingsObserver = null;
248 unregisterForWiredHeadsetIntentBroadcast();
249 unregisterBluetoothIntentsIfNeeded();
251 mIsInitialized = false;
255 * Saves current audio mode and sets audio mode to MODE_IN_COMMUNICATION
256 * if input parameter is true. Restores saved audio mode if input parameter
260 private void setCommunicationAudioModeOn(boolean on) {
261 if (DEBUG) logd("setCommunicationAudioModeOn(" + on + ")");
264 if (mSavedAudioMode != AudioManager.MODE_INVALID) {
265 Log.wtf(TAG, "Audio mode has already been set!");
269 // Store the current audio mode the first time we try to
270 // switch to communication mode.
272 mSavedAudioMode = mAudioManager.getMode();
273 } catch (SecurityException e) {
274 Log.wtf(TAG, "getMode exception: ", e);
278 // Store microphone mute state and speakerphone state so it can
279 // be restored when closing.
280 mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn();
281 mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute();
284 mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
285 } catch (SecurityException e) {
286 Log.wtf(TAG, "setMode exception: ", e);
290 if (mSavedAudioMode == AudioManager.MODE_INVALID) {
291 Log.wtf(TAG, "Audio mode has not yet been set!");
295 // Restore previously stored audio states.
296 setMicrophoneMute(mSavedIsMicrophoneMute);
297 setSpeakerphoneOn(mSavedIsSpeakerphoneOn);
299 // Restore the mode that was used before we switched to
300 // communication mode.
302 mAudioManager.setMode(mSavedAudioMode);
303 } catch (SecurityException e) {
304 Log.wtf(TAG, "setMode exception: ", e);
307 mSavedAudioMode = AudioManager.MODE_INVALID;
312 * Activates, i.e., starts routing audio to, the specified audio device.
314 * @param deviceId Unique device ID (integer converted to string)
315 * representing the selected device. This string is empty if the so-called
316 * default device is requested.
319 private boolean setDevice(String deviceId) {
320 if (DEBUG) logd("setDevice: " + deviceId);
323 int intDeviceId = deviceId.isEmpty() ?
324 DEVICE_DEFAULT : Integer.parseInt(deviceId);
326 if (intDeviceId == DEVICE_DEFAULT) {
327 boolean devices[] = null;
328 synchronized (mLock) {
329 devices = mAudioDevices.clone();
330 mRequestedAudioDevice = DEVICE_DEFAULT;
332 int defaultDevice = selectDefaultDevice(devices);
333 setAudioDevice(defaultDevice);
337 // A non-default device is specified. Verify that it is valid
338 // device, and if so, start using it.
339 List<Integer> validIds = Arrays.asList(VALID_DEVICES);
340 if (!validIds.contains(intDeviceId) || !mAudioDevices[intDeviceId]) {
343 synchronized (mLock) {
344 mRequestedAudioDevice = intDeviceId;
346 setAudioDevice(intDeviceId);
351 * @return the current list of available audio devices.
352 * Note that this call does not trigger any update of the list of devices,
353 * it only copies the current state in to the output array.
356 private AudioDeviceName[] getAudioInputDeviceNames() {
359 boolean devices[] = null;
360 synchronized (mLock) {
361 devices = mAudioDevices.clone();
363 List<String> list = new ArrayList<String>();
364 AudioDeviceName[] array =
365 new AudioDeviceName[getNumOfAudioDevices(devices)];
367 for (int id = 0; id < DEVICE_COUNT; ++id) {
369 array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]);
370 list.add(DEVICE_NAMES[id]);
374 if (DEBUG) logd("getAudioInputDeviceNames: " + list);
379 private int getNativeOutputSampleRate() {
380 if (runningOnJellyBeanMR1OrHigher()) {
381 String sampleRateString = mAudioManager.getProperty(
382 AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
383 return (sampleRateString == null ?
384 DEFAULT_SAMPLING_RATE : Integer.parseInt(sampleRateString));
386 return DEFAULT_SAMPLING_RATE;
391 * Returns the minimum frame size required for audio input.
393 * @param sampleRate sampling rate
394 * @param channels number of channels
397 private static int getMinInputFrameSize(int sampleRate, int channels) {
400 channelConfig = AudioFormat.CHANNEL_IN_MONO;
401 } else if (channels == 2) {
402 channelConfig = AudioFormat.CHANNEL_IN_STEREO;
406 return AudioRecord.getMinBufferSize(
407 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels;
411 * Returns the minimum frame size required for audio output.
413 * @param sampleRate sampling rate
414 * @param channels number of channels
417 private static int getMinOutputFrameSize(int sampleRate, int channels) {
420 channelConfig = AudioFormat.CHANNEL_OUT_MONO;
421 } else if (channels == 2) {
422 channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
426 return AudioTrack.getMinBufferSize(
427 sampleRate, channelConfig, AudioFormat.ENCODING_PCM_16BIT) / 2 / channels;
431 private boolean isAudioLowLatencySupported() {
432 return mContext.getPackageManager().hasSystemFeature(
433 PackageManager.FEATURE_AUDIO_LOW_LATENCY);
437 private int getAudioLowLatencyOutputFrameSize() {
438 String framesPerBuffer =
439 mAudioManager.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
440 return (framesPerBuffer == null ?
441 DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer));
445 public static boolean shouldUseAcousticEchoCanceler() {
446 // AcousticEchoCanceler was added in API level 16 (Jelly Bean).
447 if (!runningOnJellyBeanOrHigher()) {
451 // Next is a list of device models which have been vetted for good
452 // quality platform echo cancellation.
453 if (!Build.MODEL.equals("SM-T310R") && // Galaxy Tab 3 7.0
454 !Build.MODEL.equals("GT-I9300") && // Galaxy S3
455 !Build.MODEL.equals("GT-I9500") && // Galaxy S4
456 !Build.MODEL.equals("GT-N7105") && // Galaxy Note 2
457 !Build.MODEL.equals("SM-N9005") && // Galaxy Note 3
458 !Build.MODEL.equals("Nexus 4") &&
459 !Build.MODEL.equals("Nexus 5") &&
460 !Build.MODEL.equals("Nexus 7")) {
464 // As a final check, verify that the device supports acoustic echo
466 return AcousticEchoCanceler.isAvailable();
470 * Register for BT intents if we have the BLUETOOTH permission.
471 * Also extends the list of available devices with a BT device if one exists.
473 private void registerBluetoothIntentsIfNeeded() {
474 // Check if this process has the BLUETOOTH permission or not.
475 mHasBluetoothPermission = hasBluetoothPermission();
477 // Add a Bluetooth headset to the list of available devices if a BT
478 // headset is detected and if we have the BLUETOOTH permission.
479 // We must do this initial check using a dedicated method since the
480 // broadcasted intent BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED
481 // is not sticky and will only be received if a BT headset is connected
482 // after this method has been called.
483 if (!mHasBluetoothPermission) {
486 if (hasBluetoothHeadset()) {
487 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true;
490 // Register receivers for broadcast intents related to changes in
491 // Bluetooth headset availability and usage of the SCO channel.
492 registerForBluetoothHeadsetIntentBroadcast();
493 registerForBluetoothScoIntentBroadcast();
496 /** Unregister for BT intents if a registration has been made. */
497 private void unregisterBluetoothIntentsIfNeeded() {
498 if (mHasBluetoothPermission) {
499 mAudioManager.stopBluetoothSco();
500 unregisterForBluetoothHeadsetIntentBroadcast();
501 unregisterForBluetoothScoIntentBroadcast();
505 /** Sets the speaker phone mode. */
506 private void setSpeakerphoneOn(boolean on) {
507 boolean wasOn = mAudioManager.isSpeakerphoneOn();
511 mAudioManager.setSpeakerphoneOn(on);
514 /** Sets the microphone mute state. */
515 private void setMicrophoneMute(boolean on) {
516 boolean wasMuted = mAudioManager.isMicrophoneMute();
517 if (wasMuted == on) {
520 mAudioManager.setMicrophoneMute(on);
523 /** Gets the current microphone mute state. */
524 private boolean isMicrophoneMute() {
525 return mAudioManager.isMicrophoneMute();
528 /** Gets the current earpice state. */
529 private boolean hasEarpiece() {
530 return mContext.getPackageManager().hasSystemFeature(
531 PackageManager.FEATURE_TELEPHONY);
534 /** Checks if the process has BLUETOOTH permission or not. */
535 private boolean hasBluetoothPermission() {
536 boolean hasBluetooth = mContext.checkPermission(
537 android.Manifest.permission.BLUETOOTH,
539 Process.myUid()) == PackageManager.PERMISSION_GRANTED;
540 if (DEBUG && !hasBluetooth) {
541 logd("BLUETOOTH permission is missing!");
547 * Gets the current Bluetooth headset state.
548 * android.bluetooth.BluetoothAdapter.getProfileConnectionState() requires
549 * the BLUETOOTH permission.
551 private boolean hasBluetoothHeadset() {
552 if (!mHasBluetoothPermission) {
553 Log.wtf(TAG, "hasBluetoothHeadset() requires BLUETOOTH permission!");
557 // To get a BluetoothAdapter representing the local Bluetooth adapter,
558 // when running on JELLY_BEAN_MR1 (4.2) and below, call the static
559 // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and
560 // higher, retrieve it through getSystemService(String) with
561 // BLUETOOTH_SERVICE.
562 BluetoothAdapter btAdapter = null;
563 if (runningOnJellyBeanMR2OrHigher()) {
564 // Use BluetoothManager to get the BluetoothAdapter for
565 // Android 4.3 and above.
567 BluetoothManager btManager =
568 (BluetoothManager)mContext.getSystemService(
569 Context.BLUETOOTH_SERVICE);
570 btAdapter = btManager.getAdapter();
571 } catch (Exception e) {
572 Log.wtf(TAG, "BluetoothManager.getAdapter exception", e);
576 // Use static method for Android 4.2 and below to get the
579 btAdapter = BluetoothAdapter.getDefaultAdapter();
580 } catch (Exception e) {
581 Log.wtf(TAG, "BluetoothAdapter.getDefaultAdapter exception", e);
586 int profileConnectionState;
588 profileConnectionState = btAdapter.getProfileConnectionState(
589 android.bluetooth.BluetoothProfile.HEADSET);
590 } catch (Exception e) {
591 Log.wtf(TAG, "BluetoothAdapter.getProfileConnectionState exception", e);
592 profileConnectionState =
593 android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
596 // Ensure that Bluetooth is enabled and that a device which supports the
597 // headset and handsfree profile is connected.
598 // TODO(henrika): it is possible that btAdapter.isEnabled() is
599 // redundant. It might be sufficient to only check the profile state.
600 return btAdapter.isEnabled() && profileConnectionState ==
601 android.bluetooth.BluetoothProfile.STATE_CONNECTED;
605 * Registers receiver for the broadcasted intent when a wired headset is
606 * plugged in or unplugged. The received intent will have an extra
607 * 'state' value where 0 means unplugged, and 1 means plugged.
609 private void registerForWiredHeadsetIntentBroadcast() {
610 IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
612 /** Receiver which handles changes in wired headset availability. */
613 mWiredHeadsetReceiver = new BroadcastReceiver() {
614 private static final int STATE_UNPLUGGED = 0;
615 private static final int STATE_PLUGGED = 1;
616 private static final int HAS_NO_MIC = 0;
617 private static final int HAS_MIC = 1;
620 public void onReceive(Context context, Intent intent) {
621 int state = intent.getIntExtra("state", STATE_UNPLUGGED);
623 int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
624 String name = intent.getStringExtra("name");
625 logd("BroadcastReceiver.onReceive: a=" + intent.getAction() +
627 ", m=" + microphone +
629 ", sb=" + isInitialStickyBroadcast());
632 case STATE_UNPLUGGED:
633 synchronized (mLock) {
634 // Wired headset and earpiece are mutually exclusive.
635 mAudioDevices[DEVICE_WIRED_HEADSET] = false;
637 mAudioDevices[DEVICE_EARPIECE] = true;
642 synchronized (mLock) {
643 // Wired headset and earpiece are mutually exclusive.
644 mAudioDevices[DEVICE_WIRED_HEADSET] = true;
645 mAudioDevices[DEVICE_EARPIECE] = false;
649 loge("Invalid state!");
653 // Update the existing device selection, but only if a specific
654 // device has already been selected explicitly.
655 if (deviceHasBeenRequested()) {
656 updateDeviceActivation();
663 // Note: the intent we register for here is sticky, so it'll tell us
664 // immediately what the last action was (plugged or unplugged).
665 // It will enable us to set the speakerphone correctly.
666 mContext.registerReceiver(mWiredHeadsetReceiver, filter);
669 /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */
670 private void unregisterForWiredHeadsetIntentBroadcast() {
671 mContext.unregisterReceiver(mWiredHeadsetReceiver);
672 mWiredHeadsetReceiver = null;
676 * Registers receiver for the broadcasted intent related to BT headset
677 * availability or a change in connection state of the local Bluetooth
678 * adapter. Example: triggers when the BT device is turned on or off.
679 * BLUETOOTH permission is required to receive this one.
681 private void registerForBluetoothHeadsetIntentBroadcast() {
682 IntentFilter filter = new IntentFilter(
683 android.bluetooth.BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
685 /** Receiver which handles changes in BT headset availability. */
686 mBluetoothHeadsetReceiver = new BroadcastReceiver() {
688 public void onReceive(Context context, Intent intent) {
689 // A change in connection state of the Headset profile has
690 // been detected, e.g. BT headset has been connected or
691 // disconnected. This broadcast is *not* sticky.
692 int profileState = intent.getIntExtra(
693 android.bluetooth.BluetoothHeadset.EXTRA_STATE,
694 android.bluetooth.BluetoothHeadset.STATE_DISCONNECTED);
696 logd("BroadcastReceiver.onReceive: a=" + intent.getAction() +
697 ", s=" + profileState +
698 ", sb=" + isInitialStickyBroadcast());
701 switch (profileState) {
702 case android.bluetooth.BluetoothProfile.STATE_DISCONNECTED:
703 // We do not have to explicitly call stopBluetoothSco()
704 // since BT SCO will be disconnected automatically when
705 // the BT headset is disabled.
706 synchronized (mLock) {
707 // Remove the BT device from the list of devices.
708 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = false;
711 case android.bluetooth.BluetoothProfile.STATE_CONNECTED:
712 synchronized (mLock) {
713 // Add the BT device to the list of devices.
714 mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true;
717 case android.bluetooth.BluetoothProfile.STATE_CONNECTING:
718 // Bluetooth service is switching from off to on.
720 case android.bluetooth.BluetoothProfile.STATE_DISCONNECTING:
721 // Bluetooth service is switching from on to off.
724 loge("Invalid state!");
728 // Update the existing device selection, but only if a specific
729 // device has already been selected explicitly.
730 if (deviceHasBeenRequested()) {
731 updateDeviceActivation();
738 mContext.registerReceiver(mBluetoothHeadsetReceiver, filter);
741 private void unregisterForBluetoothHeadsetIntentBroadcast() {
742 mContext.unregisterReceiver(mBluetoothHeadsetReceiver);
743 mBluetoothHeadsetReceiver = null;
747 * Registers receiver for the broadcasted intent related the existence
748 * of a BT SCO channel. Indicates if BT SCO streaming is on or off.
750 private void registerForBluetoothScoIntentBroadcast() {
751 IntentFilter filter = new IntentFilter(
752 AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED);
754 /** BroadcastReceiver implementation which handles changes in BT SCO. */
755 mBluetoothScoReceiver = new BroadcastReceiver() {
757 public void onReceive(Context context, Intent intent) {
758 int state = intent.getIntExtra(
759 AudioManager.EXTRA_SCO_AUDIO_STATE,
760 AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
762 logd("BroadcastReceiver.onReceive: a=" + intent.getAction() +
764 ", sb=" + isInitialStickyBroadcast());
768 case AudioManager.SCO_AUDIO_STATE_CONNECTED:
769 mBluetoothScoState = STATE_BLUETOOTH_SCO_ON;
771 case AudioManager.SCO_AUDIO_STATE_DISCONNECTED:
772 mBluetoothScoState = STATE_BLUETOOTH_SCO_OFF;
774 case AudioManager.SCO_AUDIO_STATE_CONNECTING:
778 loge("Invalid state!");
786 mContext.registerReceiver(mBluetoothScoReceiver, filter);
789 private void unregisterForBluetoothScoIntentBroadcast() {
790 mContext.unregisterReceiver(mBluetoothScoReceiver);
791 mBluetoothScoReceiver = null;
794 /** Enables BT audio using the SCO audio channel. */
795 private void startBluetoothSco() {
796 if (!mHasBluetoothPermission) {
799 if (mBluetoothScoState == STATE_BLUETOOTH_SCO_ON ||
800 mBluetoothScoState == STATE_BLUETOOTH_SCO_TURNING_ON) {
801 // Unable to turn on BT in this state.
805 // Check if audio is already routed to BT SCO; if so, just update
806 // states but don't try to enable it again.
807 if (mAudioManager.isBluetoothScoOn()) {
808 mBluetoothScoState = STATE_BLUETOOTH_SCO_ON;
812 if (DEBUG) logd("startBluetoothSco: turning BT SCO on...");
813 mBluetoothScoState = STATE_BLUETOOTH_SCO_TURNING_ON;
814 mAudioManager.startBluetoothSco();
817 /** Disables BT audio using the SCO audio channel. */
818 private void stopBluetoothSco() {
819 if (!mHasBluetoothPermission) {
822 if (mBluetoothScoState != STATE_BLUETOOTH_SCO_ON &&
823 mBluetoothScoState != STATE_BLUETOOTH_SCO_TURNING_ON) {
824 // No need to turn off BT in this state.
827 if (!mAudioManager.isBluetoothScoOn()) {
828 // TODO(henrika): can we do anything else than logging here?
829 loge("Unable to stop BT SCO since it is already disabled!");
833 if (DEBUG) logd("stopBluetoothSco: turning BT SCO off...");
834 mBluetoothScoState = STATE_BLUETOOTH_SCO_TURNING_OFF;
835 mAudioManager.stopBluetoothSco();
839 * Changes selection of the currently active audio device.
841 * @param device Specifies the selected audio device.
843 private void setAudioDevice(int device) {
844 if (DEBUG) logd("setAudioDevice(device=" + device + ")");
846 // Ensure that the Bluetooth SCO audio channel is always disabled
847 // unless the BT headset device is selected.
848 if (device == DEVICE_BLUETOOTH_HEADSET) {
855 case DEVICE_BLUETOOTH_HEADSET:
857 case DEVICE_SPEAKERPHONE:
858 setSpeakerphoneOn(true);
860 case DEVICE_WIRED_HEADSET:
861 setSpeakerphoneOn(false);
863 case DEVICE_EARPIECE:
864 setSpeakerphoneOn(false);
867 loge("Invalid audio device selection!");
874 * Use a special selection scheme if the default device is selected.
875 * The "most unique" device will be selected; Wired headset first,
876 * then Bluetooth and last the speaker phone.
878 private static int selectDefaultDevice(boolean[] devices) {
879 if (devices[DEVICE_WIRED_HEADSET]) {
880 return DEVICE_WIRED_HEADSET;
881 } else if (devices[DEVICE_BLUETOOTH_HEADSET]) {
882 // TODO(henrika): possibly need improvements here if we are
883 // in a state where Bluetooth is turning off.
884 return DEVICE_BLUETOOTH_HEADSET;
886 return DEVICE_SPEAKERPHONE;
889 /** Returns true if setDevice() has been called with a valid device id. */
890 private boolean deviceHasBeenRequested() {
891 synchronized (mLock) {
892 return (mRequestedAudioDevice != DEVICE_INVALID);
897 * Updates the active device given the current list of devices and
898 * information about if a specific device has been selected or if
899 * the default device is selected.
901 private void updateDeviceActivation() {
902 boolean devices[] = null;
903 int requested = DEVICE_INVALID;
904 synchronized (mLock) {
905 requested = mRequestedAudioDevice;
906 devices = mAudioDevices.clone();
908 if (requested == DEVICE_INVALID) {
909 loge("Unable to activate device since no device is selected!");
913 // Update default device if it has been selected explicitly, or
914 // the selected device has been removed from the list.
915 if (requested == DEVICE_DEFAULT || !devices[requested]) {
916 // Get default device given current list and activate the device.
917 int defaultDevice = selectDefaultDevice(devices);
918 setAudioDevice(defaultDevice);
920 // Activate the selected device since we know that it exists in
922 setAudioDevice(requested);
926 /** Returns number of available devices */
927 private static int getNumOfAudioDevices(boolean[] devices) {
929 for (int i = 0; i < DEVICE_COUNT; ++i) {
937 * For now, just log the state change but the idea is that we should
938 * notify a registered state change listener (if any) that there has
939 * been a change in the state.
940 * TODO(henrika): add support for state change listener.
942 private void reportUpdate() {
943 synchronized (mLock) {
944 List<String> devices = new ArrayList<String>();
945 for (int i = 0; i < DEVICE_COUNT; ++i) {
946 if (mAudioDevices[i])
947 devices.add(DEVICE_NAMES[i]);
950 logd("reportUpdate: requested=" + mRequestedAudioDevice +
951 ", btSco=" + mBluetoothScoState +
952 ", devices=" + devices);
957 private void logDeviceInfo() {
958 Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER +
959 " Board: " + Build.BOARD + " Device: " + Build.DEVICE +
960 " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT);
963 /** Trivial helper method for debug logging */
964 private static void logd(String msg) {
968 /** Trivial helper method for error logging */
969 private static void loge(String msg) {
973 private class SettingsObserver extends ContentObserver {
974 SettingsObserver(Handler handler) {
976 mContentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, this);
980 public void onChange(boolean selfChange) {
981 if (DEBUG) logd("SettingsObserver.onChange: " + selfChange);
982 super.onChange(selfChange);
983 int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
984 if (DEBUG) logd("nativeSetMute: " + (volume == 0));
985 nativeSetMute(mNativeAudioManagerAndroid, (volume == 0));
989 private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted);