summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorhenrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-29 20:00:00 +0000
committerhenrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-29 20:00:00 +0000
commit5ff853ecbdc86dcb25249d84d87d0afeb7caad43 (patch)
tree7b7899906a8e725c0b283773296062c2171120d5 /media
parent9b8da4a39ad6c94b2f464c418bf844cc3ede0d6d (diff)
downloadchromium_src-5ff853ecbdc86dcb25249d84d87d0afeb7caad43.zip
chromium_src-5ff853ecbdc86dcb25249d84d87d0afeb7caad43.tar.gz
chromium_src-5ff853ecbdc86dcb25249d84d87d0afeb7caad43.tar.bz2
Adding device enumeration to the Android device manager.
Main goal is to support the getSources API (see TEST) and to allow VoIP clients in Chrome to use Bluetooth headsets. BUG=324464 TEST=https://simpl.info/getusermedia/sources/ and manual unittests in media. Review URL: https://codereview.chromium.org/78033003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237942 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r--media/audio/android/audio_manager_android.cc71
-rw-r--r--media/audio/android/audio_manager_android.h5
-rw-r--r--media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java476
3 files changed, 496 insertions, 56 deletions
diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc
index dc77a4c..3466510 100644
--- a/media/audio/android/audio_manager_android.cc
+++ b/media/audio/android/audio_manager_android.cc
@@ -4,7 +4,12 @@
#include "media/audio/android/audio_manager_android.h"
+#include "base/android/build_info.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
#include "jni/AudioManagerAndroid_jni.h"
#include "media/audio/android/opensles_input.h"
#include "media/audio/android/opensles_output.h"
@@ -13,6 +18,12 @@
#include "media/audio/fake_audio_input_stream.h"
#include "media/base/channel_layout.h"
+using base::android::AppendJavaStringArrayToStringVector;
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::ScopedJavaLocalRef;
+
namespace media {
static void AddDefaultDevice(AudioDeviceNames* device_names) {
@@ -42,9 +53,11 @@ AudioManagerAndroid::AudioManagerAndroid() {
Java_AudioManagerAndroid_createAudioManagerAndroid(
base::android::AttachCurrentThread(),
base::android::GetApplicationContext()));
+ Init();
}
AudioManagerAndroid::~AudioManagerAndroid() {
+ Close();
Shutdown();
}
@@ -58,11 +71,31 @@ bool AudioManagerAndroid::HasAudioInputDevices() {
void AudioManagerAndroid::GetAudioInputDeviceNames(
AudioDeviceNames* device_names) {
+ // Always add default device parameters as first element.
AddDefaultDevice(device_names);
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobjectArray> j_device_array =
+ Java_AudioManagerAndroid_getAudioInputDeviceNames(
+ env, j_audio_manager_.obj());
+ jsize len = env->GetArrayLength(j_device_array.obj());
+ AudioDeviceName device;
+ for (jsize i = 0; i < len; ++i) {
+ ScopedJavaLocalRef<jobject> j_device(
+ env, env->GetObjectArrayElement(j_device_array.obj(), i));
+ ScopedJavaLocalRef<jstring> j_device_name =
+ Java_AudioDeviceName_name(env, j_device.obj());
+ ConvertJavaStringToUTF8(env, j_device_name.obj(), &device.device_name);
+ ScopedJavaLocalRef<jstring> j_device_id =
+ Java_AudioDeviceName_id(env, j_device.obj());
+ ConvertJavaStringToUTF8(env, j_device_id.obj(), &device.unique_id);
+ device_names->push_back(device);
+ }
}
void AudioManagerAndroid::GetAudioOutputDeviceNames(
AudioDeviceNames* device_names) {
+ // TODO(henrika): enumerate using GetAudioInputDeviceNames().
AddDefaultDevice(device_names);
}
@@ -91,7 +124,6 @@ AudioOutputStream* AudioManagerAndroid::MakeAudioOutputStream(
std::string());
if (stream && output_stream_count() == 1) {
SetAudioMode(kAudioModeInCommunication);
- RegisterHeadsetReceiver();
}
return stream;
}
@@ -106,7 +138,6 @@ AudioInputStream* AudioManagerAndroid::MakeAudioInputStream(
void AudioManagerAndroid::ReleaseOutputStream(AudioOutputStream* stream) {
AudioManagerBase::ReleaseOutputStream(stream);
if (!output_stream_count()) {
- UnregisterHeadsetReceiver();
SetAudioMode(kAudioModeNormal);
}
}
@@ -139,6 +170,12 @@ AudioInputStream* AudioManagerAndroid::MakeLinearInputStream(
AudioInputStream* AudioManagerAndroid::MakeLowLatencyInputStream(
const AudioParameters& params, const std::string& device_id) {
DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format());
+ DLOG_IF(ERROR, device_id.empty()) << "Invalid device ID!";
+ // Utilize the device ID to select the correct input device.
+ // Note that the input device is always associated with a certain output
+ // device, i.e., this selection does also switch the output device.
+ // All input and output streams will be affected by the device selection.
+ SetAudioDevice(device_id);
return new OpenSLESInputStream(this, params);
}
@@ -188,22 +225,36 @@ bool AudioManagerAndroid::RegisterAudioManager(JNIEnv* env) {
return RegisterNativesImpl(env);
}
-void AudioManagerAndroid::SetAudioMode(int mode) {
- Java_AudioManagerAndroid_setMode(
+void AudioManagerAndroid::Init() {
+ Java_AudioManagerAndroid_init(
base::android::AttachCurrentThread(),
- j_audio_manager_.obj(), mode);
+ j_audio_manager_.obj());
}
-void AudioManagerAndroid::RegisterHeadsetReceiver() {
- Java_AudioManagerAndroid_registerHeadsetReceiver(
+void AudioManagerAndroid::Close() {
+ Java_AudioManagerAndroid_close(
base::android::AttachCurrentThread(),
j_audio_manager_.obj());
}
-void AudioManagerAndroid::UnregisterHeadsetReceiver() {
- Java_AudioManagerAndroid_unregisterHeadsetReceiver(
+void AudioManagerAndroid::SetAudioMode(int mode) {
+ Java_AudioManagerAndroid_setMode(
base::android::AttachCurrentThread(),
- j_audio_manager_.obj());
+ j_audio_manager_.obj(), mode);
+}
+
+void AudioManagerAndroid::SetAudioDevice(const std::string& device_id) {
+ JNIEnv* env = AttachCurrentThread();
+
+ // Send the unique device ID to the Java audio manager and make the
+ // device switch. Provide an empty string to the Java audio manager
+ // if the default device is selected.
+ ScopedJavaLocalRef<jstring> j_device_id = ConvertUTF8ToJavaString(
+ env,
+ device_id == AudioManagerBase::kDefaultDeviceId ?
+ std::string() : device_id);
+ Java_AudioManagerAndroid_setDevice(
+ env, j_audio_manager_.obj(), j_device_id.obj());
}
int AudioManagerAndroid::GetNativeOutputSampleRate() {
diff --git a/media/audio/android/audio_manager_android.h b/media/audio/android/audio_manager_android.h
index ed2b2c3..cb90416 100644
--- a/media/audio/android/audio_manager_android.h
+++ b/media/audio/android/audio_manager_android.h
@@ -60,9 +60,10 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase {
const AudioParameters& input_params) OVERRIDE;
private:
+ void Init();
+ void Close();
void SetAudioMode(int mode);
- void RegisterHeadsetReceiver();
- void UnregisterHeadsetReceiver();
+ void SetAudioDevice(const std::string& device_id);
int GetNativeOutputSampleRate();
bool IsAudioLowLatencySupported();
int GetAudioLowLatencyOutputFrameSize();
diff --git a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
index 0f0cfb6..db674f5 100644
--- a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
+++ b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
@@ -4,6 +4,8 @@
package org.chromium.media;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -14,8 +16,15 @@ import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.os.Build;
+import android.os.Process;
import android.util.Log;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
@@ -23,30 +32,95 @@ import org.chromium.base.JNINamespace;
class AudioManagerAndroid {
private static final String TAG = "AudioManagerAndroid";
- // Most of Google lead devices use 44.1K as the default sampling rate, 44.1K
- // is also widely used on other android devices.
+ // Set to true to enable debug logs. Always check in as false.
+ private static final boolean DEBUG = false;
+
+ /** Simple container for device information. */
+ private static class AudioDeviceName {
+ private final int mId;
+ private final String mName;
+
+ private AudioDeviceName(int id, String name) {
+ mId = id;
+ mName = name;
+ }
+
+ @CalledByNative("AudioDeviceName")
+ private String id() { return String.valueOf(mId); }
+
+ @CalledByNative("AudioDeviceName")
+ private String name() { return mName; }
+ }
+
+ // Supported audio device types.
+ private static final int DEVICE_INVALID = -1;
+ private static final int DEVICE_SPEAKERPHONE = 0;
+ private static final int DEVICE_WIRED_HEADSET = 1;
+ private static final int DEVICE_EARPIECE = 2;
+ private static final int DEVICE_BLUETOOTH_HEADSET = 3;
+ private static final int DEVICE_COUNT = 4;
+
+ // Maps audio device types to string values. This map must be in sync
+ // with the device types above.
+ // TODO(henrika): add support for proper detection of device names and
+ // localize the name strings by using resource strings.
+ private static final String[] DEVICE_NAMES = new String[] {
+ "Speakerphone",
+ "Wired headset", // With or without microphone
+ "Headset earpiece", // Only available on mobile phones
+ "Bluetooth headset",
+ };
+
+ // List of valid device types.
+ private static Integer[] VALID_DEVICES = new Integer[] {
+ DEVICE_SPEAKERPHONE,
+ DEVICE_WIRED_HEADSET,
+ DEVICE_EARPIECE,
+ DEVICE_BLUETOOTH_HEADSET,
+ };
+
+ // The device does not have any audio device.
+ static final int STATE_NO_DEVICE_SELECTED = 0;
+ // The speakerphone is on and an associated microphone is used.
+ static final int STATE_SPEAKERPHONE_ON = 1;
+ // The phone's earpiece is on and an associated microphone is used.
+ static final int STATE_EARPIECE_ON = 2;
+ // A wired headset (with or without a microphone) is plugged in.
+ static final int STATE_WIRED_HEADSET_ON = 3;
+ // The audio stream is being directed to a Bluetooth headset.
+ static final int STATE_BLUETOOTH_ON = 4;
+ // We've requested that the audio stream be directed to Bluetooth, but
+ // have not yet received a response from the framework.
+ static final int STATE_BLUETOOTH_TURNING_ON = 5;
+ // We've requested that the audio stream stop being directed to
+ // Bluetooth, but have not yet received a response from the framework.
+ static final int STATE_BLUETOOTH_TURNING_OFF = 6;
+ // TODO(henrika): document the valid state transitions.
+
+ // Use 44.1kHz as the default sampling rate.
private static final int DEFAULT_SAMPLING_RATE = 44100;
// Randomly picked up frame size which is close to return value on N4.
- // Return this default value when
- // getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) fails.
+ // Return this value when getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
+ // fails.
private static final int DEFAULT_FRAME_PER_BUFFER = 256;
private final AudioManager mAudioManager;
private final Context mContext;
- private BroadcastReceiver mReceiver;
- private boolean mOriginalSpeakerStatus;
+ private boolean mHasBluetoothPermission = false;
+ private boolean mIsInitialized = false;
+ private boolean mSavedIsSpeakerphoneOn;
+ private boolean mSavedIsMicrophoneMute;
- @CalledByNative
- public void setMode(int mode) {
- try {
- mAudioManager.setMode(mode);
- } catch (SecurityException e) {
- Log.e(TAG, "setMode exception: " + e.getMessage());
- logDeviceInfo();
- }
- }
+ private Integer mAudioDeviceState = STATE_NO_DEVICE_SELECTED;
+
+ // Contains a list of currently available audio devices.
+ private boolean[] mAudioDevices = new boolean[DEVICE_COUNT];
+ // Broadcast receiver for wired headset intent broadcasts.
+ private BroadcastReceiver mWiredHeadsetReceiver;
+
+ /** Construction */
@CalledByNative
private static AudioManagerAndroid createAudioManagerAndroid(Context context) {
return new AudioManagerAndroid(context);
@@ -57,46 +131,135 @@ class AudioManagerAndroid {
mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
}
+ /**
+ * Saves the initial speakerphone and microphone state.
+ * Populates the list of available audio devices and registers receivers
+ * for broadcasted intents related to wired headset and bluetooth devices.
+ */
@CalledByNative
- public void registerHeadsetReceiver() {
- if (mReceiver != null) {
+ public void init() {
+ if (mIsInitialized)
return;
+
+ for (int i = 0; i < DEVICE_COUNT; ++i) {
+ mAudioDevices[i] = false;
}
- mOriginalSpeakerStatus = mAudioManager.isSpeakerphoneOn();
- if (!mOriginalSpeakerStatus) {
- mAudioManager.setSpeakerphoneOn(true);
+ // Store microphone mute state and speakerphone state so it can
+ // be restored when closing.
+ mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn();
+ mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute();
+
+ // Always enable speaker phone by default. This state might be reset
+ // by the wired headset receiver when it gets its initial sticky
+ // intent, if any.
+ setSpeakerphoneOn(true);
+ mAudioDeviceState = STATE_SPEAKERPHONE_ON;
+
+ // Initialize audio device list with things we know is always available.
+ if (hasEarpiece()) {
+ mAudioDevices[DEVICE_EARPIECE] = true;
}
- IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+ mAudioDevices[DEVICE_SPEAKERPHONE] = true;
- mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_HEADSET_PLUG.equals(intent.getAction())) {
- try {
- mAudioManager.setSpeakerphoneOn(
- intent.getIntExtra("state", 0) == 0);
- } catch (SecurityException e) {
- Log.e(TAG, "setMode exception: " + e.getMessage());
- logDeviceInfo();
- }
- }
- }
- };
- mContext.registerReceiver(mReceiver, filter);
+ // Register receiver for broadcasted intents related to adding/
+ // removing a wired headset (Intent.ACTION_HEADSET_PLUG).
+ // Also starts routing to the wired headset/headphone if one is
+ // already attached (can be overridden by a Bluetooth headset).
+ registerForWiredHeadsetIntentBroadcast();
+
+ // Start routing to Bluetooth if there's a connected device.
+ // TODO(henrika): the actual routing part is not implemented yet.
+ // All we do currently is to detect if BT headset is attached or not.
+ initBluetooth();
+
+ mIsInitialized = true;
}
+ /**
+ * Unregister all previously registered intent receivers and restore
+ * the stored state (stored in {@link #init()}).
+ */
@CalledByNative
- public void unregisterHeadsetReceiver() {
- mContext.unregisterReceiver(mReceiver);
- mReceiver = null;
- mAudioManager.setSpeakerphoneOn(mOriginalSpeakerStatus);
+ public void close() {
+ if (!mIsInitialized)
+ return;
+
+ unregisterForWiredHeadsetIntentBroadcast();
+
+ // Restore previously stored audio states.
+ setMicrophoneMute(mSavedIsMicrophoneMute);
+ setSpeakerphoneOn(mSavedIsSpeakerphoneOn);
+
+ mIsInitialized = false;
}
- private void logDeviceInfo() {
- Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER +
- " Board: " + Build.BOARD + " Device: " + Build.DEVICE +
- " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT);
+ @CalledByNative
+ public void setMode(int mode) {
+ try {
+ mAudioManager.setMode(mode);
+ } catch (SecurityException e) {
+ Log.e(TAG, "setMode exception: " + e.getMessage());
+ logDeviceInfo();
+ }
+ }
+
+ /**
+ * Activates, i.e., starts routing audio to, the specified audio device.
+ *
+ * @param deviceId Unique device ID (integer converted to string)
+ * representing the selected device. This string is empty if the so-called
+ * default device is selected.
+ */
+ @CalledByNative
+ public void setDevice(String deviceId) {
+ if (deviceId.isEmpty()) {
+ logd("setDevice: default");
+ // Use a special selection scheme if the default device is selected.
+ // The "most unique" device will be selected; Bluetooth first, then
+ // wired headset and last the speaker phone.
+ if (mAudioDevices[DEVICE_BLUETOOTH_HEADSET]) {
+ // TODO(henrika): possibly need improvements here if we are
+ // in a STATE_BLUETOOTH_TURNING_OFF state.
+ setAudioDevice(DEVICE_BLUETOOTH_HEADSET);
+ } else if (mAudioDevices[DEVICE_WIRED_HEADSET]) {
+ setAudioDevice(DEVICE_WIRED_HEADSET);
+ } else {
+ setAudioDevice(DEVICE_SPEAKERPHONE);
+ }
+ } else {
+ logd("setDevice: " + deviceId);
+ // A non-default device is specified. Verify that it is valid
+ // device, and if so, start using it.
+ List<Integer> validIds = Arrays.asList(VALID_DEVICES);
+ Integer id = Integer.valueOf(deviceId);
+ if (validIds.contains(id)) {
+ setAudioDevice(id.intValue());
+ } else {
+ loge("Invalid device ID!");
+ }
+ }
+ }
+
+ /**
+ * @return the current list of available audio devices.
+ * Note that this call does not trigger any update of the list of devices,
+ * it only copies the current state in to the output array.
+ */
+ @CalledByNative
+ public AudioDeviceName[] getAudioInputDeviceNames() {
+ List<String> devices = new ArrayList<String>();
+ AudioDeviceName[] array = new AudioDeviceName[getNumOfAudioDevices()];
+ int i = 0;
+ for (int id = 0; id < DEVICE_COUNT; ++id ) {
+ if (mAudioDevices[id]) {
+ array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]);
+ devices.add(DEVICE_NAMES[id]);
+ i++;
+ }
+ }
+ logd("getAudioInputDeviceNames: " + devices);
+ return array;
}
@CalledByNative
@@ -165,4 +328,229 @@ class AudioManagerAndroid {
DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer));
}
+ /** Sets the speaker phone mode. */
+ public void setSpeakerphoneOn(boolean on) {
+ boolean wasOn = mAudioManager.isSpeakerphoneOn();
+ if (wasOn == on) {
+ return;
+ }
+ mAudioManager.setSpeakerphoneOn(on);
+ }
+
+ /** Sets the microphone mute state. */
+ public void setMicrophoneMute(boolean on) {
+ boolean wasMuted = mAudioManager.isMicrophoneMute();
+ if (wasMuted == on) {
+ return;
+ }
+ mAudioManager.setMicrophoneMute(on);
+ }
+
+ /** Gets the current microphone mute state. */
+ public boolean isMicrophoneMute() {
+ return mAudioManager.isMicrophoneMute();
+ }
+
+ /** Gets the current earpice state. */
+ private boolean hasEarpiece() {
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY);
+ }
+
+ /**
+ * Registers receiver for the broadcasted intent when a wired headset is
+ * plugged in or unplugged. The received intent will have an extra
+ * 'state' value where 0 means unplugged, and 1 means plugged.
+ */
+ private void registerForWiredHeadsetIntentBroadcast() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_HEADSET_PLUG);
+
+ /**
+ * Receiver which handles changes in wired headset availablilty.
+ */
+ mWiredHeadsetReceiver = new BroadcastReceiver() {
+ private static final int STATE_UNPLUGGED = 0;
+ private static final int STATE_PLUGGED = 1;
+ private static final int HAS_NO_MIC = 0;
+ private static final int HAS_MIC = 1;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (!action.equals(Intent.ACTION_HEADSET_PLUG))
+ return;
+ int state = intent.getIntExtra("state", STATE_UNPLUGGED);
+ int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
+ String name = intent.getStringExtra("name");
+ logd("==> onReceive: s=" + state
+ + ", m=" + microphone
+ + ", n=" + name
+ + ", s=" + isInitialStickyBroadcast());
+
+ switch (state) {
+ case STATE_UNPLUGGED:
+ // Wired headset and earpiece are mutually exclusive.
+ mAudioDevices[DEVICE_WIRED_HEADSET] = false;
+ if (hasEarpiece()) {
+ mAudioDevices[DEVICE_EARPIECE] = true;
+ }
+ // If wired headset was used before it was unplugged,
+ // switch to speaker phone. If it was not in use; just
+ // log the change.
+ if (mAudioDeviceState == STATE_WIRED_HEADSET_ON) {
+ setAudioDevice(DEVICE_SPEAKERPHONE);
+ } else {
+ reportUpdate();
+ }
+ break;
+ case STATE_PLUGGED:
+ // Wired headset and earpiece are mutually exclusive.
+ mAudioDevices[DEVICE_WIRED_HEADSET] = true;
+ mAudioDevices[DEVICE_EARPIECE] = false;
+ setAudioDevice(DEVICE_WIRED_HEADSET);
+ break;
+ default:
+ loge("Invalid state!");
+ break;
+ }
+ }
+ };
+
+ // Note: the intent we register for here is sticky, so it'll tell us
+ // immediately what the last action was (plugged or unplugged).
+ // It will enable us to set the speakerphone correctly.
+ mContext.registerReceiver(mWiredHeadsetReceiver, filter);
+ }
+
+ /** Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent. */
+ private void unregisterForWiredHeadsetIntentBroadcast() {
+ mContext.unregisterReceiver(mWiredHeadsetReceiver);
+ mWiredHeadsetReceiver = null;
+ }
+
+ /**
+ * Check if Bluetooth device is connected, register Bluetooth receiver
+ * and start routing to Bluetooth if a device is connected.
+ * TODO(henrika): currently only supports the detecion part at startup.
+ */
+ private void initBluetooth() {
+ // Bail out if we don't have the required permission.
+ mHasBluetoothPermission = mContext.checkPermission(
+ android.Manifest.permission.BLUETOOTH,
+ Process.myPid(),
+ Process.myUid()) == PackageManager.PERMISSION_GRANTED;
+ if (!mHasBluetoothPermission) {
+ loge("BLUETOOTH permission is missing!");
+ return;
+ }
+
+ // To get a BluetoothAdapter representing the local Bluetooth adapter,
+ // when running on JELLY_BEAN_MR1 (4.2) and below, call the static
+ // getDefaultAdapter() method; when running on JELLY_BEAN_MR2 (4.3) and
+ // higher, retrieve it through getSystemService(String) with
+ // BLUETOOTH_SERVICE.
+ // Note: Most methods require the BLUETOOTH permission.
+ BluetoothAdapter btAdapter = null;
+ if (android.os.Build.VERSION.SDK_INT <=
+ android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ // Use static method for Android 4.2 and below to get the
+ // BluetoothAdapter.
+ btAdapter = BluetoothAdapter.getDefaultAdapter();
+ } else {
+ // Use BluetoothManager to get the BluetoothAdapter for
+ // Android 4.3 and above.
+ BluetoothManager btManager =
+ (BluetoothManager)mContext.getSystemService(
+ Context.BLUETOOTH_SERVICE);
+ btAdapter = btManager.getAdapter();
+ }
+
+ if (btAdapter != null &&
+ // android.bluetooth.BluetoothAdapter.getProfileConnectionState
+ // requires BLUETOOTH permission.
+ android.bluetooth.BluetoothProfile.STATE_CONNECTED ==
+ btAdapter.getProfileConnectionState(
+ android.bluetooth.BluetoothProfile.HEADSET)) {
+ mAudioDevices[DEVICE_BLUETOOTH_HEADSET] = true;
+ // TODO(henrika): ensure that we set the active audio
+ // device to Bluetooth (not trivial).
+ setAudioDevice(DEVICE_BLUETOOTH_HEADSET);
+ }
+ }
+
+ /**
+ * Changes selection of the currently active audio device.
+ *
+ * @param device Specifies the selected audio device.
+ */
+ public void setAudioDevice(int device) {
+ switch (device) {
+ case DEVICE_BLUETOOTH_HEADSET:
+ // TODO(henrika): add support for turning on an routing to
+ // BT here.
+ if (DEBUG) logd("--- TO BE IMPLEMENTED ---");
+ break;
+ case DEVICE_SPEAKERPHONE:
+ // TODO(henrika): turn off BT if required.
+ mAudioDeviceState = STATE_SPEAKERPHONE_ON;
+ setSpeakerphoneOn(true);
+ break;
+ case DEVICE_WIRED_HEADSET:
+ // TODO(henrika): turn off BT if required.
+ mAudioDeviceState = STATE_WIRED_HEADSET_ON;
+ setSpeakerphoneOn(false);
+ break;
+ case DEVICE_EARPIECE:
+ // TODO(henrika): turn off BT if required.
+ mAudioDeviceState = STATE_EARPIECE_ON;
+ setSpeakerphoneOn(false);
+ break;
+ default:
+ loge("Invalid audio device selection!");
+ break;
+ }
+ reportUpdate();
+ }
+
+ private int getNumOfAudioDevices() {
+ int count = 0;
+ for (int i = 0; i < DEVICE_COUNT; ++i) {
+ if (mAudioDevices[i])
+ count++;
+ }
+ return count;
+ }
+
+ /**
+ * For now, just log the state change but the idea is that we should
+ * notifies a registered state change listener (if any) that there has
+ * been a change in the state.
+ * TODO(henrika): add support for state change listener.
+ */
+ private void reportUpdate() {
+ List<String> devices = new ArrayList<String>();
+ for (int i = 0; i < DEVICE_COUNT; ++i) {
+ if (mAudioDevices[i])
+ devices.add(DEVICE_NAMES[i]);
+ }
+ logd("reportUpdate: state=" + mAudioDeviceState
+ + ", devices=" + devices);
+ }
+
+ private void logDeviceInfo() {
+ Log.i(TAG, "Manufacturer:" + Build.MANUFACTURER +
+ " Board: " + Build.BOARD + " Device: " + Build.DEVICE +
+ " Model: " + Build.MODEL + " PRODUCT: " + Build.PRODUCT);
+ }
+
+ /** Trivial helper method for debug logging */
+ private void logd(String msg) {
+ Log.d(TAG, msg);
+ }
+
+ /** Trivial helper method for error logging */
+ private void loge(String msg) {
+ Log.e(TAG, msg);
+ }
}