| Index: media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
|
| 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 0f0cfb61e08428eb477c9de493d4d60fff342293..faf884abe37c529bd846576a2930516b273f46d0 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;
|
| @@ -16,6 +18,12 @@ import android.media.AudioTrack;
|
| import android.os.Build;
|
| 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,6 +31,39 @@ import org.chromium.base.JNINamespace;
|
| class AudioManagerAndroid {
|
| private static final String TAG = "AudioManagerAndroid";
|
|
|
| + /**
|
| + * Lists the possible types (and names) of audio device that we support
|
| + * on Android.
|
| + */
|
| + private enum AudioDevice {
|
| + SPEAKERPHONE("Speaker"),
|
| + WIRED_HEADSET("Wired headphones"),
|
| + EARPIECE("Handset earpice"),
|
| + BLUETOOTH_HEADSET("Bluetooth headset");
|
| +
|
| + private final String name;
|
| +
|
| + private AudioDevice(String s) {
|
| + name = s;
|
| + }
|
| +
|
| + @Override
|
| + public String toString() {
|
| + return name;
|
| + }
|
| +
|
| + public AudioDevice fromString(String name) {
|
| + if (name == null)
|
| + return null;
|
| + for (AudioDevice a : AudioDevice.values()) {
|
| + if (name.equalsIgnoreCase(a.name)) {
|
| + return a;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| + }
|
| +
|
| // Most of Google lead devices use 44.1K as the default sampling rate, 44.1K
|
| // is also widely used on other android devices.
|
| private static final int DEFAULT_SAMPLING_RATE = 44100;
|
| @@ -31,12 +72,63 @@ class AudioManagerAndroid {
|
| // getProperty(PROPERTY_OUTPUT_FRAMES_PER_BUFFER) fails.
|
| private static final int DEFAULT_FRAME_PER_BUFFER = 256;
|
|
|
| + private boolean mIsInitialized = false;
|
| +
|
| private final AudioManager mAudioManager;
|
| private final Context mContext;
|
|
|
| private BroadcastReceiver mReceiver;
|
| private boolean mOriginalSpeakerStatus;
|
|
|
| + // Use Set to ensure that we don't store duplicates. Ignores order.
|
| + private Set<AudioDevice> mAudioDevices = new HashSet<AudioDevice>();
|
| +
|
| + /**
|
| + * Class to handle changes in wired headset availablilty.
|
| + *
|
| + * Note that isInitialStickyBroadcast() returns true if the receiver is
|
| + * currently processing the initial value of a sticky broadcast. It can
|
| + * happen during startup when the cached state us pushed out to us an
|
| + * initial "sticky broadcast".
|
| + */
|
| + private BroadcastReceiver mWiredHeadsetReceiver = new BroadcastReceiver() {
|
| + private static final int STATE_UNPLUGGED = 0;
|
| + private static final int STATE_PLUGGED = 1;
|
| +
|
| + @Override
|
| + public void onReceive(Context context, Intent intent) {
|
| + String action = intent.getAction();
|
| + if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
|
| + int state = intent.getIntExtra("state", STATE_UNPLUGGED);
|
| +
|
| + logd("==> WiredHeadsetReceiver.onReceive: state=" + state
|
| + + ", isInitialStickyBroadcast="
|
| + + isInitialStickyBroadcast());
|
| +
|
| + switch (state) {
|
| + case STATE_UNPLUGGED:
|
| + // Wired headset and earpiece are mutually-exclusive.
|
| + mAudioDevices.remove(AudioDevice.WIRED_HEADSET);
|
| + if (hasEarpiece()) {
|
| + mAudioDevices.add(AudioDevice.EARPIECE);
|
| + }
|
| + // TODO(henrika): check if wired headset was used before
|
| + // it was unlpugged. If so, switch to speaker phone.
|
| + // If'it was not in use, don't do anything.
|
| + // Also, turn off BT if it was active at this stage.
|
| + break;
|
| + case STATE_PLUGGED:
|
| + // Wired headset and earpiece are mutually-exclusive.
|
| + mAudioDevices.add(AudioDevice.WIRED_HEADSET);
|
| + mAudioDevices.remove(AudioDevice.EARPIECE);
|
| + // TODO(henrika): ensure that the wired headset is active.
|
| + // Possibly turn BT off and also disable the speaker phone.
|
| + break;
|
| + }
|
| + }
|
| + }
|
| + };
|
| +
|
| @CalledByNative
|
| public void setMode(int mode) {
|
| try {
|
| @@ -57,6 +149,51 @@ class AudioManagerAndroid {
|
| mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
|
| }
|
|
|
| + /**
|
| + * Pupulate the list of available audio devices and register receivers
|
| + * for broadcasted intents related to wired headset and bluetooth devices.
|
| + * TODO(henrika): we should probably store an initial entry state here
|
| + * as well.
|
| + */
|
| + @CalledByNative
|
| + public void init() {
|
| + if (mIsInitialized)
|
| + return;
|
| +
|
| + // Initialize audio device list with things we know is always available.
|
| + if (hasEarpiece()) {
|
| + mAudioDevices.add(AudioDevice.EARPIECE);
|
| + }
|
| + mAudioDevices.add(AudioDevice.SPEAKERPHONE);
|
| +
|
| + registerForWiredHeadsetIntentBroadcast();
|
| + initBluetooth();
|
| +
|
| + logd("init: devices=" + mAudioDevices);
|
| + mIsInitialized = true;
|
| + }
|
| +
|
| + /**
|
| + * Unregister all previously registered intent receivers.
|
| + * TODO(henrika): we should probably restore the initial entry state here
|
| + * as well.
|
| + */
|
| + @CalledByNative
|
| + public void close() {
|
| + if (!mIsInitialized)
|
| + return;
|
| +
|
| + logd("close");
|
| + unregisterForWiredHeadsetIntentBroadcast();
|
| + mIsInitialized = false;
|
| + }
|
| +
|
| + @CalledByNative
|
| + public String[] getAudioOutputDeviceNames() {
|
| + logd("getAudioOutputDeviceNames");
|
| + return toStringArray(mAudioDevices);
|
| + }
|
| +
|
| @CalledByNative
|
| public void registerHeadsetReceiver() {
|
| if (mReceiver != null) {
|
| @@ -165,4 +302,101 @@ class AudioManagerAndroid {
|
| DEFAULT_FRAME_PER_BUFFER : Integer.parseInt(framesPerBuffer));
|
| }
|
|
|
| + /**
|
| + * Returns true if the device has a headset earpice and false if not.
|
| + */
|
| + private boolean hasEarpiece() {
|
| + boolean hasFeature = mContext.getPackageManager().hasSystemFeature(
|
| + PackageManager.FEATURE_TELEPHONY);
|
| + return hasFeature;
|
| + }
|
| +
|
| + /**
|
| + * Converts a set of audio devices into array of strings.
|
| + */
|
| + private static String[] toStringArray(Set<AudioDevice> devices) {
|
| + if (devices == null) {
|
| + return null;
|
| + }
|
| + String[] retVal = new String[devices.size()];
|
| + int i = 0;
|
| + for (AudioDevice device : devices) {
|
| + retVal[i] = device.toString();
|
| + i++;
|
| + }
|
| + return retVal;
|
| + }
|
| +
|
| + /**
|
| + * 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() {
|
| + logd("registerForWiredHeadsetIntentBroadcast");
|
| + IntentFilter filter = new IntentFilter();
|
| + filter.addAction(Intent.ACTION_HEADSET_PLUG);
|
| + mContext.registerReceiver(mWiredHeadsetReceiver, filter);
|
| + // Note: the intent we just registered for 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.
|
| + }
|
| +
|
| + /**
|
| + * Unregister receiver for broadcasted ACTION_HEADSET_PLUG intent.
|
| + */
|
| + private void unregisterForWiredHeadsetIntentBroadcast() {
|
| + logd("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.
|
| + */
|
| + private void initBluetooth() {
|
| + logd("initBluetooth");
|
| +
|
| + // 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.BluetoothProfile.STATE_CONNECTED ==
|
| + btAdapter.getProfileConnectionState(
|
| + android.bluetooth.BluetoothProfile.HEADSET)) {
|
| + logd("BT device was connected at start of call");
|
| + mAudioDevices.add(AudioDevice.BLUETOOTH_HEADSET);
|
| + // TODO(henrika): ensure that we set the active audio
|
| + // device to Bluetooth (not trivial).
|
| + // setAudioDevice(AudioDevice.BLUETOOTH_HEADSET);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Trivial helper method for debug logging.
|
| + */
|
| + private void logd(String msg) {
|
| + Log.d(TAG, msg);
|
| + }
|
| }
|
|
|