Chromium Code Reviews| 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); |
| + } |
| } |