diff options
author | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 09:47:23 +0000 |
---|---|---|
committer | henrika@chromium.org <henrika@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-17 09:47:23 +0000 |
commit | 5fa8ab6d9ab2a072fde9f73cf77de9d20c6204fd (patch) | |
tree | 2866b9c3dc57a65069a92bfdd4ccc9ad0c8b3856 | |
parent | 015b30332db40f8de944660b35b6cbaae3a8971f (diff) | |
download | chromium_src-5fa8ab6d9ab2a072fde9f73cf77de9d20c6204fd.zip chromium_src-5fa8ab6d9ab2a072fde9f73cf77de9d20c6204fd.tar.gz chromium_src-5fa8ab6d9ab2a072fde9f73cf77de9d20c6204fd.tar.bz2 |
Reland "Refactor audio manager for Android to avoid heavy tasks at startup"
Original CL: https://codereview.chromium.org/110173003/.
Refactor audio manager for Android to avoid heavy tasks at startup.
Main goal of this CL is to avoid all demanding tasks (e.g. initiate audio routing, detect BT devices etc.) at Chrome startup.
All we do now is to populate the list of available devices. BT support has been removed but will be added in an upcoming CL.
NOTRY=True
TBR=bulach, tommi
BUG=324464
TEST=media_unittests --gtest-filter=AudioAndroid*
Review URL: https://codereview.chromium.org/117073002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241247 0039d316-1c4b-4281-b951-d872f2087c98
5 files changed, 306 insertions, 240 deletions
diff --git a/build/android/findbugs_filter/findbugs_known_bugs.txt b/build/android/findbugs_filter/findbugs_known_bugs.txt index 79b618b..03999bc 100644 --- a/build/android/findbugs_filter/findbugs_known_bugs.txt +++ b/build/android/findbugs_filter/findbugs_known_bugs.txt @@ -25,8 +25,6 @@ M D SF: Switch statement found in org.chromium.chrome.browser.database.SQLiteCur M D SF: Switch statement found in org.chromium.content.browser.third_party.GestureDetector.onTouchEvent(MotionEvent) where default case is missing At GestureDetector.java M M UG: org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.getBooleanValue() is unsynchronized, org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.setBooleanValue(boolean) is synchronized At JavaBridgeReturnValuesTest.java M M UG: org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.getStringValue() is unsynchronized, org.chromium.content.browser.JavaBridgeReturnValuesTest$TestObject.setStringValue(String) is synchronized At JavaBridgeReturnValuesTest.java -M M UW: Unconditional wait in org.chromium.media.AudioManagerAndroid.init() At AudioManagerAndroid.java -M M Wa: Wait not in loop in org.chromium.media.AudioManagerAndroid.init() At AudioManagerAndroid.java M V EI2: new org.chromium.chrome.browser.FindMatchRectsDetails(int, RectF[], RectF) may expose internal representation by storing an externally mutable object into FindMatchRectsDetails.rects At FindMatchRectsDetails.java M V EI2: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.setFavicon(byte[]) may expose internal representation by storing an externally mutable object into ChromeBrowserProvider$BookmarkNode.mFavicon At ChromeBrowserProvider.java M V EI2: org.chromium.chrome.browser.ChromeBrowserProvider$BookmarkNode.setThumbnail(byte[]) may expose internal representation by storing an externally mutable object into ChromeBrowserProvider$BookmarkNode.mThumbnail At ChromeBrowserProvider.java diff --git a/media/audio/android/audio_android_unittest.cc b/media/audio/android/audio_android_unittest.cc index e791326..8f453c6 100644 --- a/media/audio/android/audio_android_unittest.cc +++ b/media/audio/android/audio_android_unittest.cc @@ -85,6 +85,41 @@ static double ExpectedTimeBetweenCallbacks(AudioParameters params) { static_cast<double>(params.sample_rate()))).InMillisecondsF(); } +// Helper method which verifies that the device list starts with a valid +// default device name followed by non-default device names. +static void CheckDeviceNames(const AudioDeviceNames& device_names) { + VLOG(2) << "Got " << device_names.size() << " audio devices."; + if (device_names.empty()) { + // Log a warning so we can see the status on the build bots. No need to + // break the test though since this does successfully test the code and + // some failure cases. + LOG(WARNING) << "No input devices detected"; + return; + } + + AudioDeviceNames::const_iterator it = device_names.begin(); + + // The first device in the list should always be the default device. + EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceName), + it->device_name); + EXPECT_EQ(std::string(AudioManagerBase::kDefaultDeviceId), it->unique_id); + ++it; + + // Other devices should have non-empty name and id and should not contain + // default name or id. + while (it != device_names.end()) { + EXPECT_FALSE(it->device_name.empty()); + EXPECT_FALSE(it->unique_id.empty()); + VLOG(2) << "Device ID(" << it->unique_id + << "), label: " << it->device_name; + EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceName), + it->device_name); + EXPECT_NE(std::string(AudioManagerBase::kDefaultDeviceId), + it->unique_id); + ++it; + } +} + std::ostream& operator<<(std::ostream& os, const AudioParameters& params) { using namespace std; os << endl << "format: " << FormatToString(params.format()) << endl @@ -567,6 +602,24 @@ TEST_F(AudioAndroidOutputTest, IsAudioLowLatencySupported) { : VLOG(0) << "Low latency output is *not* supported"; } +// Verify input device enumeration. +TEST_F(AudioAndroidInputTest, GetAudioInputDeviceNames) { + if (!audio_manager()->HasAudioInputDevices()) + return; + AudioDeviceNames devices; + audio_manager()->GetAudioInputDeviceNames(&devices); + CheckDeviceNames(devices); +} + +// Verify output device enumeration. +TEST_F(AudioAndroidOutputTest, GetAudioOutputDeviceNames) { + if (!audio_manager()->HasAudioOutputDevices()) + return; + AudioDeviceNames devices; + audio_manager()->GetAudioOutputDeviceNames(&devices); + CheckDeviceNames(devices); +} + // Ensure that a default input stream can be created and closed. TEST_P(AudioAndroidInputTest, CreateAndCloseInputStream) { AudioParameters params = GetInputStreamParameters(); diff --git a/media/audio/android/audio_manager_android.cc b/media/audio/android/audio_manager_android.cc index 3464d89..e67700c 100644 --- a/media/audio/android/audio_manager_android.cc +++ b/media/audio/android/audio_manager_android.cc @@ -37,9 +37,6 @@ static void AddDefaultDevice(AudioDeviceNames* device_names) { // Maximum number of output streams that can be open simultaneously. static const int kMaxOutputStreams = 10; -static const int kAudioModeNormal = 0x00000000; -static const int kAudioModeInCommunication = 0x00000003; - static const int kDefaultInputBufferSize = 1024; static const int kDefaultOutputBufferSize = 2048; @@ -126,12 +123,17 @@ AudioOutputStream* AudioManagerAndroid::MakeAudioOutputStream( const AudioParameters& params, const std::string& device_id, const std::string& input_device_id) { + bool had_no_streams = HadNoAudioStreams(); AudioOutputStream* stream = AudioManagerBase::MakeAudioOutputStream(params, std::string(), std::string()); - if (stream && output_stream_count() == 1) { - SetAudioMode(kAudioModeInCommunication); - } + + // The audio manager for Android creates streams intended for real-time + // VoIP sessions and therefore sets the audio mode to MODE_IN_COMMUNICATION. + // If a Bluetooth headset is used, the audio stream will use the SCO + // channel and therefore have a limited bandwidth (8-16kHz). + if (stream && had_no_streams) + SetCommunicationAudioModeOn(true); { base::AutoLock lock(streams_lock_); @@ -143,22 +145,37 @@ AudioOutputStream* AudioManagerAndroid::MakeAudioOutputStream( AudioInputStream* AudioManagerAndroid::MakeAudioInputStream( const AudioParameters& params, const std::string& device_id) { + bool had_no_streams = HadNoAudioStreams(); AudioInputStream* stream = AudioManagerBase::MakeAudioInputStream(params, device_id); + + // The audio manager for Android creates streams intended for real-time + // VoIP sessions and therefore sets the audio mode to MODE_IN_COMMUNICATION. + // If a Bluetooth headset is used, the audio stream will use the SCO + // channel and therefore have a limited bandwidth (8kHz). + if (stream && had_no_streams) + SetCommunicationAudioModeOn(true); return stream; } void AudioManagerAndroid::ReleaseOutputStream(AudioOutputStream* stream) { AudioManagerBase::ReleaseOutputStream(stream); - if (!output_stream_count()) { - SetAudioMode(kAudioModeNormal); - } + + // Restore the audio mode which was used before the first communication- + // mode stream was created. + if (HadNoAudioStreams()) + SetCommunicationAudioModeOn(false); base::AutoLock lock(streams_lock_); streams_.erase(static_cast<OpenSLESOutputStream*>(stream)); } void AudioManagerAndroid::ReleaseInputStream(AudioInputStream* stream) { AudioManagerBase::ReleaseInputStream(stream); + + // Restore the audio mode which was used before the first communication- + // mode stream was created. + if (HadNoAudioStreams()) + SetCommunicationAudioModeOn(false); } AudioOutputStream* AudioManagerAndroid::MakeLinearOutputStream( @@ -189,11 +206,14 @@ AudioInputStream* AudioManagerAndroid::MakeLowLatencyInputStream( const AudioParameters& params, const std::string& device_id) { DCHECK_EQ(AudioParameters::AUDIO_PCM_LOW_LATENCY, params.format()); DLOG_IF(ERROR, device_id.empty()) << "Invalid device ID!"; - // Utilize the device ID to select the correct input device. + // Use the device ID to select the correct input device. // Note that the input device is always associated with a certain output // device, i.e., this selection does also switch the output device. // All input and output streams will be affected by the device selection. - SetAudioDevice(device_id); + if (!SetAudioDevice(device_id)) { + LOG(ERROR) << "Unable to select audio device!"; + return NULL; + } if (params.effects() != AudioParameters::NO_EFFECTS) { // Platform effects can only be enabled through the AudioRecord path. @@ -252,6 +272,10 @@ AudioParameters AudioManagerAndroid::GetPreferredOutputStreamParameters( sample_rate, bits_per_sample, buffer_size, AudioParameters::NO_EFFECTS); } +bool AudioManagerAndroid::HadNoAudioStreams() { + return output_stream_count() == 0 && input_stream_count() == 0; +} + // static bool AudioManagerAndroid::RegisterAudioManager(JNIEnv* env) { return RegisterNativesImpl(env); @@ -286,13 +310,13 @@ void AudioManagerAndroid::DoSetMuteOnAudioThread(bool muted) { } } -void AudioManagerAndroid::SetAudioMode(int mode) { - Java_AudioManagerAndroid_setMode( +void AudioManagerAndroid::SetCommunicationAudioModeOn(bool on) { + Java_AudioManagerAndroid_setCommunicationAudioModeOn( base::android::AttachCurrentThread(), - j_audio_manager_.obj(), mode); + j_audio_manager_.obj(), on); } -void AudioManagerAndroid::SetAudioDevice(const std::string& device_id) { +bool AudioManagerAndroid::SetAudioDevice(const std::string& device_id) { JNIEnv* env = AttachCurrentThread(); // Send the unique device ID to the Java audio manager and make the @@ -302,7 +326,7 @@ void AudioManagerAndroid::SetAudioDevice(const std::string& device_id) { env, device_id == AudioManagerBase::kDefaultDeviceId ? std::string() : device_id); - Java_AudioManagerAndroid_setDevice( + return Java_AudioManagerAndroid_setDevice( env, j_audio_manager_.obj(), j_device_id.obj()); } diff --git a/media/audio/android/audio_manager_android.h b/media/audio/android/audio_manager_android.h index 2900c0f..670c094 100644 --- a/media/audio/android/audio_manager_android.h +++ b/media/audio/android/audio_manager_android.h @@ -67,10 +67,11 @@ class MEDIA_EXPORT AudioManagerAndroid : public AudioManagerBase { const AudioParameters& input_params) OVERRIDE; private: + bool HadNoAudioStreams(); void Init(); void Close(); - void SetAudioMode(int mode); - void SetAudioDevice(const std::string& device_id); + void SetCommunicationAudioModeOn(bool on); + bool SetAudioDevice(const std::string& device_id); int GetNativeOutputSampleRate(); bool IsAudioLowLatencySupported(); int GetAudioLowLatencyOutputFrameSize(); 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 afaa035..15d7aef 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,8 +18,7 @@ import android.media.AudioTrack; import android.media.audiofx.AcousticEchoCanceler; import android.os.Build; import android.os.Handler; -import android.os.Looper; -import android.os.Process; +import android.os.HandlerThread; import android.provider.Settings; import android.util.Log; @@ -57,6 +54,7 @@ class AudioManagerAndroid { } // 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; @@ -83,24 +81,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. @@ -112,15 +92,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 requested audio device. Can only be modified by + // call to setDevice(). + private int mRequestedAudioDevice = DEVICE_INVALID; - // Lock to protect |mAudioDevices| which can be accessed from the main - // thread and the audio manager thread. + // Lock to protect |mAudioDevices| and |mRequestedAudioDevice| which can + // be accessed from the main thread and the audio manager thread. private final Object mLock = new Object(); // Contains a list of currently available audio devices. @@ -128,9 +111,8 @@ class AudioManagerAndroid { private final ContentResolver mContentResolver; private SettingsObserver mSettingsObserver = null; - private SettingsObserverThread mSettingsObserverThread = null; + private HandlerThread mSettingsObserverThread = null; private int mCurrentVolume; - private final Object mSettingsObserverLock = new Object(); // Broadcast receiver for wired headset intent broadcasts. private BroadcastReceiver mWiredHeadsetReceiver; @@ -157,56 +139,31 @@ 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(); + mSettingsObserverThread = new HandlerThread("SettingsObserver"); + mSettingsObserverThread.start(); + mSettingsObserver = new SettingsObserver( + new Handler(mSettingsObserverThread.getLooper())); mIsInitialized = true; - - mSettingsObserverThread = new SettingsObserverThread(); - mSettingsObserverThread.start(); - synchronized (mSettingsObserverLock) { - try { - mSettingsObserverLock.wait(); - } catch (InterruptedException e) { - Log.e(TAG, "unregisterHeadsetReceiver exception: " + e.getMessage()); - } - } + if (DEBUG) reportUpdate(); } /** @@ -215,33 +172,79 @@ class AudioManagerAndroid { */ @CalledByNative public void close() { + if (DEBUG) logd("close"); if (!mIsInitialized) return; - if (mSettingsObserverThread != null) { - mSettingsObserverThread = null; - } - if (mSettingsObserver != null) { - mContentResolver.unregisterContentObserver(mSettingsObserver); - mSettingsObserver = null; + mSettingsObserverThread.quit(); + try { + mSettingsObserverThread.join(); + } catch (InterruptedException e) { + logwtf("HandlerThread.join() exception: " + e.getMessage()); } + mSettingsObserverThread = null; + mContentResolver.unregisterContentObserver(mSettingsObserver); + mSettingsObserver = null; 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(); + private 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; } } @@ -250,40 +253,36 @@ class AudioManagerAndroid { * * @param deviceId Unique device ID (integer converted to string) * representing the selected device. This string is empty if the so-called - * default device is selected. + * default device is requested. */ @CalledByNative - public void 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); - // 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)) { - setAudioDevice(id.intValue()); - } else { - loge("Invalid device ID!"); + private boolean setDevice(String deviceId) { + if (DEBUG) logd("setDevice: " + deviceId); + int intDeviceId = deviceId.isEmpty() ? + DEVICE_DEFAULT : Integer.parseInt(deviceId); + + if (intDeviceId == DEVICE_DEFAULT) { + boolean devices[] = null; + synchronized (mLock) { + devices = mAudioDevices.clone(); + mRequestedAudioDevice = DEVICE_DEFAULT; } + int defaultDevice = selectDefaultDevice(devices); + setAudioDevice(defaultDevice); + return true; } + + // 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); + if (!validIds.contains(intDeviceId) || !mAudioDevices[intDeviceId]) { + return false; + } + synchronized (mLock) { + mRequestedAudioDevice = intDeviceId; + } + setAudioDevice(intDeviceId); + return true; } /** @@ -293,20 +292,23 @@ class AudioManagerAndroid { */ @CalledByNative public AudioDeviceName[] getAudioInputDeviceNames() { + boolean devices[] = null; synchronized (mLock) { - List<String> devices = new ArrayList<String>(); - AudioDeviceName[] array = new AudioDeviceName[getNumOfAudioDevicesWithLock()]; - int i = 0; - for (int id = 0; id < DEVICE_COUNT; ++id) { - if (mAudioDevices[id]) { - array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); - devices.add(DEVICE_NAMES[id]); - i++; - } + devices = mAudioDevices.clone(); + } + List<String> list = new ArrayList<String>(); + AudioDeviceName[] array = + new AudioDeviceName[getNumOfAudioDevices(devices)]; + int i = 0; + for (int id = 0; id < DEVICE_COUNT; ++id) { + if (devices[id]) { + array[i] = new AudioDeviceName(id, DEVICE_NAMES[id]); + list.add(DEVICE_NAMES[id]); + i++; } - logd("getAudioInputDeviceNames: " + devices); - return array; } + if (DEBUG) logd("getAudioInputDeviceNames: " + list); + return array; } @CalledByNative @@ -387,7 +389,7 @@ class AudioManagerAndroid { } /** Sets the speaker phone mode. */ - public void setSpeakerphoneOn(boolean on) { + private void setSpeakerphoneOn(boolean on) { boolean wasOn = mAudioManager.isSpeakerphoneOn(); if (wasOn == on) { return; @@ -396,7 +398,7 @@ class AudioManagerAndroid { } /** Sets the microphone mute state. */ - public void setMicrophoneMute(boolean on) { + private void setMicrophoneMute(boolean on) { boolean wasMuted = mAudioManager.isMicrophoneMute(); if (wasMuted == on) { return; @@ -405,7 +407,7 @@ class AudioManagerAndroid { } /** Gets the current microphone mute state. */ - public boolean isMicrophoneMute() { + private boolean isMicrophoneMute() { return mAudioManager.isMicrophoneMute(); } @@ -424,7 +426,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; @@ -439,13 +443,14 @@ class AudioManagerAndroid { return; } 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) { + int microphone = intent.getIntExtra("microphone", HAS_NO_MIC); + String name = intent.getStringExtra("name"); + logd("BroadcastReceiver.onReceive: s=" + state + ", m=" + microphone + ", n=" + name + ", sb=" + isInitialStickyBroadcast()); - + } switch (state) { case STATE_UNPLUGGED: synchronized (mLock) { @@ -455,27 +460,30 @@ 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 deviceHasBeenRequested = false; + synchronized (mLock) { + deviceHasBeenRequested = (mRequestedAudioDevice != DEVICE_INVALID); + } + if (deviceHasBeenRequested) { + updateDeviceActivation(); + } else if (DEBUG) { + reportUpdate(); + } } }; @@ -492,62 +500,11 @@ 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) { + private void setAudioDevice(int device) { switch (device) { case DEVICE_BLUETOOTH_HEADSET: // TODO(henrika): add support for turning on an routing to @@ -556,17 +513,14 @@ class AudioManagerAndroid { break; case DEVICE_SPEAKERPHONE: // TODO(henrika): turn off BT if required. - mAudioDeviceState = STATE_SPEAKERPHONE_ON; setSpeakerphoneOn(true); break; case DEVICE_WIRED_HEADSET: // TODO(henrika): turn off BT if required. - mAudioDeviceState = STATE_WIRED_HEADSET_ON; setSpeakerphoneOn(false); break; case DEVICE_EARPIECE: // TODO(henrika): turn off BT if required. - mAudioDeviceState = STATE_EARPIECE_ON; setSpeakerphoneOn(false); break; default: @@ -576,11 +530,58 @@ class AudioManagerAndroid { reportUpdate(); } - private int getNumOfAudioDevicesWithLock() { + /** + * 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. + */ + private static int selectDefaultDevice(boolean[] devices) { + 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; + } + + /** + * 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 requested = DEVICE_INVALID; + synchronized (mLock) { + requested = mRequestedAudioDevice; + devices = mAudioDevices.clone(); + } + if (requested == 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 (requested == DEVICE_DEFAULT || !devices[requested]) { + // 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(requested); + } + } + + /** Returns number of available devices */ + private static int getNumOfAudioDevices(boolean[] devices) { int count = 0; for (int i = 0; i < DEVICE_COUNT; ++i) { - if (mAudioDevices[i]) - count++; + if (devices[i]) + ++count; } return count; } @@ -598,8 +599,10 @@ class AudioManagerAndroid { if (mAudioDevices[i]) devices.add(DEVICE_NAMES[i]); } - logd("reportUpdate: state=" + mAudioDeviceState - + ", devices=" + devices); + if (DEBUG) { + logd("reportUpdate: requested=" + mRequestedAudioDevice + + ", devices=" + devices); + } } } @@ -619,39 +622,26 @@ 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()); + SettingsObserver(Handler handler) { + super(handler); mContentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, this); } @Override public void onChange(boolean selfChange) { + if (DEBUG) logd("SettingsObserver.onChange: " + selfChange); super.onChange(selfChange); int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL); + if (DEBUG) logd("nativeSetMute: " + (volume == 0)); nativeSetMute(mNativeAudioManagerAndroid, (volume == 0)); } } private native void nativeSetMute(long nativeAudioManagerAndroid, boolean muted); - - private class SettingsObserverThread extends Thread { - SettingsObserverThread() { - super("SettinsObserver"); - } - - @Override - public void run() { - // Set this thread up so the handler will work on it. - Looper.prepare(); - - synchronized (mSettingsObserverLock) { - mSettingsObserver = new SettingsObserver(); - mSettingsObserverLock.notify(); - } - - // Listen for volume change. - Looper.loop(); - } - } } |