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 c61b86af70acdf248839fb2733e1043ad6b4d116..a7338813947ce1f2e3e7dcf8a65101f90d05e548 100644 |
| --- a/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java |
| +++ b/media/base/android/java/src/org/chromium/media/AudioManagerAndroid.java |
| @@ -36,7 +36,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 +55,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 +83,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 +94,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 +145,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(); |
| 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 +182,7 @@ class AudioManagerAndroid { |
| */ |
| @CalledByNative |
| public void close() { |
| + if (DEBUG) logd("close"); |
| if (!mIsInitialized) |
| return; |
| @@ -227,20 +196,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 +264,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 = getDefaultDevice(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 +309,7 @@ class AudioManagerAndroid { |
| i++; |
| } |
| } |
| - logd("getAudioInputDeviceNames: " + devices); |
| + if (DEBUG) logd("getAudioInputDeviceNames: " + devices); |
| return array; |
| } |
| } |
| @@ -412,7 +418,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 +437,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 +452,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 +490,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 +507,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 +527,20 @@ class AudioManagerAndroid { |
| reportUpdate(); |
| } |
| + private static int getDefaultDevice(boolean[] devices) { |
|
tommi (sloooow) - chröme
2013/12/12 11:43:15
nit: what about calling this selectDefaultDevice?
henrika (OOO until Aug 14)
2013/12/12 12:53:11
Done.
|
| + // 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 +551,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 = getDefaultDevice(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 +602,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 +626,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() { |
| super(new Handler()); |
| @@ -625,7 +649,7 @@ class AudioManagerAndroid { |
| private class SettingsObserverThread extends Thread { |
| SettingsObserverThread() { |
| - super("SettinsObserver"); |
| + super("SettingsObserver"); |
| } |
| @Override |