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. |
Jói
2013/11/25 13:09:35
unlpugged -> unplugged
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
|
+ // If'it was not in use, don't do anything. |
Jói
2013/11/25 13:09:35
If'it -> If it
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
|
+ // 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 |
Jói
2013/11/25 13:09:35
Pupulate -> Populate
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
|
+ * 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. |
Jói
2013/11/25 13:09:35
I think the convention for JavaDoc comments is for
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
|
+ */ |
+ 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"); |
Jói
2013/11/25 13:09:35
The indent here seems off, shouldn't it be 4 in fr
henrika (OOO until Aug 14)
2013/11/25 13:28:14
Done.
|
+ 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); |
+ } |
} |