Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(880)

Unified Diff: media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java

Issue 110173003: Refactor audio manager for Android to avoid heavy tasks at startup (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: nits Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 c61b86af70acdf248839fb2733e1043ad6b4d116..3dd23d5ee3668eb0573e63f770932849daaf4a12 100644
--- a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
+++ b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java
@@ -4,8 +4,6 @@
package org.chromium.media;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -20,7 +18,6 @@ import android.media.AudioTrack;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.os.Process;
import android.provider.Settings;
import android.util.Log;
@@ -36,7 +33,7 @@ class AudioManagerAndroid {
private static final String TAG = "AudioManagerAndroid";
// Set to true to enable debug logs. Always check in as false.
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
/** Simple container for device information. */
private static class AudioDeviceName {
@@ -55,7 +52,8 @@ class AudioManagerAndroid {
private String name() { return mName; }
}
- // Supported audio device types.
+ // Supported audio device types.
+ private static final int DEVICE_DEFAULT = -2;
private static final int DEVICE_INVALID = -1;
private static final int DEVICE_SPEAKERPHONE = 0;
private static final int DEVICE_WIRED_HEADSET = 1;
@@ -82,24 +80,6 @@ class AudioManagerAndroid {
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.
@@ -111,12 +91,18 @@ class AudioManagerAndroid {
private final Context mContext;
private final long mNativeAudioManagerAndroid;
- private boolean mHasBluetoothPermission = false;
+ private int mSavedAudioMode = AudioManager.MODE_INVALID;
+
private boolean mIsInitialized = false;
private boolean mSavedIsSpeakerphoneOn;
private boolean mSavedIsMicrophoneMute;
- private Integer mAudioDeviceState = STATE_NO_DEVICE_SELECTED;
+ // Id of the currently active audio device, i.e., audio is routed to
+ // this device.
+ private int mActiveAudioDevice = DEVICE_INVALID;
+ // Id of the currently selected audio device. Can only be modified by
+ // call to setDevice().
+ private int mSelectedAudioDevice = DEVICE_INVALID;
// Lock to protect |mAudioDevices| which can be accessed from the main
// thread and the audio manager thread.
@@ -156,56 +142,35 @@ class AudioManagerAndroid {
*/
@CalledByNative
public void init() {
+ if (DEBUG) logd("init");
if (mIsInitialized)
return;
- synchronized (mLock) {
- for (int i = 0; i < DEVICE_COUNT; ++i) {
- mAudioDevices[i] = false;
- }
+ for (int i = 0; i < DEVICE_COUNT; ++i) {
+ mAudioDevices[i] = false;
}
- // 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.
- synchronized (mLock) {
- if (hasEarpiece()) {
- mAudioDevices[DEVICE_EARPIECE] = true;
- }
- mAudioDevices[DEVICE_SPEAKERPHONE] = true;
+ if (hasEarpiece()) {
+ mAudioDevices[DEVICE_EARPIECE] = true;
}
+ mAudioDevices[DEVICE_SPEAKERPHONE] = true;
// 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;
-
mSettingsObserverThread = new SettingsObserverThread();
tommi (sloooow) - chröme 2013/12/12 14:29:45 Change lines 163-171 to: mSettingsObserverThread
mSettingsObserverThread.start();
synchronized (mSettingsObserverLock) {
try {
mSettingsObserverLock.wait();
} catch (InterruptedException e) {
- Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage());
+ loge("Object.wait exception: " + e.getMessage());
}
}
+
+ mIsInitialized = true;
}
/**
@@ -214,6 +179,7 @@ class AudioManagerAndroid {
*/
@CalledByNative
public void close() {
+ if (DEBUG) logd("close");
if (!mIsInitialized)
return;
@@ -227,20 +193,63 @@ class AudioManagerAndroid {
unregisterForWiredHeadsetIntentBroadcast();
- // Restore previously stored audio states.
- setMicrophoneMute(mSavedIsMicrophoneMute);
- setSpeakerphoneOn(mSavedIsSpeakerphoneOn);
-
mIsInitialized = false;
}
+ /**
+ * Saves current audio mode and sets audio mode to MODE_IN_COMMUNICATION
+ * if input parameter is true. Restores saved audio mode if input parameter
+ * is false.
+ */
@CalledByNative
- public void setMode(int mode) {
- try {
- mAudioManager.setMode(mode);
- } catch (SecurityException e) {
- Log.e(TAG, "setMode exception: " + e.getMessage());
- logDeviceInfo();
+ public void setCommunicationAudioModeOn(boolean on) {
+ if (DEBUG) logd("setCommunicationAudioModeOn(" + on + ")");
+
+ if (on) {
+ if (mSavedAudioMode != AudioManager.MODE_INVALID) {
+ logwtf("Audio mode has already been set!");
+ return;
+ }
+
+ // Store the current audio mode the first time we try to
+ // switch to communication mode.
+ try {
+ mSavedAudioMode = mAudioManager.getMode();
+ } catch (SecurityException e) {
+ logwtf("getMode exception: " + e.getMessage());
+ logDeviceInfo();
+ }
+
+ // Store microphone mute state and speakerphone state so it can
+ // be restored when closing.
+ mSavedIsSpeakerphoneOn = mAudioManager.isSpeakerphoneOn();
+ mSavedIsMicrophoneMute = mAudioManager.isMicrophoneMute();
+
+ try {
+ mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+ } catch (SecurityException e) {
+ logwtf("setMode exception: " + e.getMessage());
+ logDeviceInfo();
+ }
+ } else {
+ if (mSavedAudioMode == AudioManager.MODE_INVALID) {
+ logwtf("Audio mode has not yet been set!");
+ return;
+ }
+
+ // Restore previously stored audio states.
+ setMicrophoneMute(mSavedIsMicrophoneMute);
+ setSpeakerphoneOn(mSavedIsSpeakerphoneOn);
+
+ // Restore the mode that was used before we switched to
+ // communication mode.
+ try {
+ mAudioManager.setMode(mSavedAudioMode);
+ } catch (SecurityException e) {
+ logwtf("setMode exception: " + e.getMessage());
+ logDeviceInfo();
+ }
+ mSavedAudioMode = AudioManager.MODE_INVALID;
}
}
@@ -252,37 +261,31 @@ class AudioManagerAndroid {
* default device is selected.
*/
@CalledByNative
- public void setDevice(String deviceId) {
+ boolean setDevice(String deviceId) {
boolean devices[] = null;
synchronized (mLock) {
devices = mAudioDevices.clone();
}
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 (devices[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 (devices[DEVICE_WIRED_HEADSET]) {
- setAudioDevice(DEVICE_WIRED_HEADSET);
- } else {
- setAudioDevice(DEVICE_SPEAKERPHONE);
- }
- } else {
- logd("setDevice: " + deviceId);
+ if (DEBUG) logd("setDevice: default");
+ int defaultDevice = selectDefaultDevice(devices);
+ setAudioDevice(defaultDevice);
+ mSelectedAudioDevice = DEVICE_DEFAULT;
+ return true;
+ } else if (isNumeric(deviceId)) {
+ if (DEBUG) 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)) {
+ if (validIds.contains(id) && mAudioDevices[id.intValue()]) {
+ mSelectedAudioDevice = id.intValue();
setAudioDevice(id.intValue());
- } else {
- loge("Invalid device ID!");
+ return true;
}
}
+ loge("Invalid device ID: " + deviceId);
+ return false;
}
/**
@@ -303,7 +306,7 @@ class AudioManagerAndroid {
i++;
}
}
- logd("getAudioInputDeviceNames: " + devices);
+ if (DEBUG) logd("getAudioInputDeviceNames: " + devices);
return array;
}
}
@@ -412,7 +415,9 @@ class AudioManagerAndroid {
IntentFilter filter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
/**
- * Receiver which handles changes in wired headset availablilty.
+ * Receiver which handles changes in wired headset availability:
+ * updates the list of devices;
+ * updates the active device if a device selection has been made.
*/
mWiredHeadsetReceiver = new BroadcastReceiver() {
private static final int STATE_UNPLUGGED = 0;
@@ -429,11 +434,12 @@ class AudioManagerAndroid {
int state = intent.getIntExtra("state", STATE_UNPLUGGED);
int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
String name = intent.getStringExtra("name");
- logd("==> onReceive: s=" + state
+ if (DEBUG) {
+ logd("==> onReceive: s=" + state
+ ", m=" + microphone
+ ", n=" + name
+ ", sb=" + isInitialStickyBroadcast());
-
+ }
switch (state) {
case STATE_UNPLUGGED:
synchronized (mLock) {
@@ -443,27 +449,28 @@ class AudioManagerAndroid {
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:
synchronized (mLock) {
// 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;
}
+
+ // Update the existing device selection, but only if a specific
+ // device has already been selected explicitly.
+ boolean deviceHasBeenSelected = false;
+ synchronized (mLock) {
+ deviceHasBeenSelected = (mSelectedAudioDevice != DEVICE_INVALID);
+ }
+ if (deviceHasBeenSelected) {
+ updateDeviceActivation();
+ }
}
};
@@ -480,62 +487,15 @@ class AudioManagerAndroid {
}
/**
- * 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)) {
- synchronized (mLock) {
- 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) {
+ if (device == mActiveAudioDevice) {
+ if (DEBUG) logd("setAudioDevice: " + device + " is already active");
+ return;
+ }
switch (device) {
case DEVICE_BLUETOOTH_HEADSET:
// TODO(henrika): add support for turning on an routing to
@@ -544,17 +504,17 @@ class AudioManagerAndroid {
break;
case DEVICE_SPEAKERPHONE:
// TODO(henrika): turn off BT if required.
- mAudioDeviceState = STATE_SPEAKERPHONE_ON;
+ mActiveAudioDevice = DEVICE_SPEAKERPHONE;
setSpeakerphoneOn(true);
break;
case DEVICE_WIRED_HEADSET:
// TODO(henrika): turn off BT if required.
- mAudioDeviceState = STATE_WIRED_HEADSET_ON;
+ mActiveAudioDevice = DEVICE_WIRED_HEADSET;
setSpeakerphoneOn(false);
break;
case DEVICE_EARPIECE:
// TODO(henrika): turn off BT if required.
- mAudioDeviceState = STATE_EARPIECE_ON;
+ mActiveAudioDevice = DEVICE_EARPIECE;
setSpeakerphoneOn(false);
break;
default:
@@ -564,6 +524,20 @@ class AudioManagerAndroid {
reportUpdate();
}
+ private static int selectDefaultDevice(boolean[] devices) {
+ // Use a special selection scheme if the default device is selected.
+ // The "most unique" device will be selected; Wired headset first,
+ // then Bluetooth and last the speaker phone.
+ if (devices[DEVICE_WIRED_HEADSET]) {
+ return DEVICE_WIRED_HEADSET;
+ } else if (devices[DEVICE_BLUETOOTH_HEADSET]) {
+ // TODO(henrika): possibly need improvements here if we are
+ // in a state where Bluetooth is turning off.
+ return DEVICE_BLUETOOTH_HEADSET;
+ }
+ return DEVICE_SPEAKERPHONE;
+ }
+
private int getNumOfAudioDevicesWithLock() {
int count = 0;
for (int i = 0; i < DEVICE_COUNT; ++i) {
@@ -574,6 +548,45 @@ class AudioManagerAndroid {
}
/**
+ * Updates the active device given the current list of devices and
+ * information about if a specific device has been selected or if
+ * the default device is selected.
+ */
+ private void updateDeviceActivation() {
+ boolean devices[] = null;
+ int selected = DEVICE_INVALID;
+ synchronized (mLock) {
+ selected = mSelectedAudioDevice;
+ devices = mAudioDevices.clone();
+ }
+ if (selected == DEVICE_INVALID) {
+ loge("Unable to activate device since no device is selected!");
+ return;
+ }
+
+ // Update default device if it has been selected explicitly, or
+ // the selected device has been removed from the list.
+ if (selected == DEVICE_DEFAULT || !devices[mSelectedAudioDevice]) {
+ // Get default device given current list and activate the device.
+ int defaultDevice = selectDefaultDevice(devices);
+ setAudioDevice(defaultDevice);
+ } else {
+ // Activate the selected device since we know that it exists in
+ // the list.
+ setAudioDevice(selected);
+ }
+ }
+
+ private static boolean isNumeric(String str) {
+ for (char c : str.toCharArray()) {
+ if (!Character.isDigit(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
* For now, just log the state change but the idea is that we should
* notify a registered state change listener (if any) that there has
* been a change in the state.
@@ -586,8 +599,11 @@ class AudioManagerAndroid {
if (mAudioDevices[i])
devices.add(DEVICE_NAMES[i]);
}
- logd("reportUpdate: state=" + mAudioDeviceState
- + ", devices=" + devices);
+ if (DEBUG) {
+ logd("reportUpdate: active=" + mActiveAudioDevice
+ + ", selected=" + mSelectedAudioDevice
+ + ", devices=" + devices);
+ }
}
}
@@ -607,6 +623,11 @@ class AudioManagerAndroid {
Log.e(TAG, msg);
}
+ /** What a Terrible Failure: Reports a condition that should never happen */
+ private void logwtf(String msg) {
+ Log.wtf(TAG, msg);
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver() {
tommi (sloooow) - chröme 2013/12/12 14:29:45 change the constructor to be: SettingsObserver(Ha
super(new Handler());
@@ -625,7 +646,7 @@ class AudioManagerAndroid {
private class SettingsObserverThread extends Thread {
tommi (sloooow) - chröme 2013/12/12 14:29:45 Remove this class.
henrika (OOO until Aug 14) 2013/12/12 15:39:15 Done.
SettingsObserverThread() {
- super("SettinsObserver");
+ super("SettingsObserver");
}
@Override

Powered by Google App Engine
This is Rietveld 408576698