summaryrefslogtreecommitdiffstats
path: root/chromeos
diff options
context:
space:
mode:
authorjennyz <jennyz@chromium.org>2016-03-02 12:15:52 -0800
committerCommit bot <commit-bot@chromium.org>2016-03-02 20:17:16 +0000
commitbd23674643e0993e443ef785e80b9ad2e3e9adf4 (patch)
treeceacaa531bf0eee1923686dcf5c6b74d5ecdee99 /chromeos
parent66a4306ee83b007997120be98638487a617938d8 (diff)
downloadchromium_src-bd23674643e0993e443ef785e80b9ad2e3e9adf4.zip
chromium_src-bd23674643e0993e443ef785e80b9ad2e3e9adf4.tar.gz
chromium_src-bd23674643e0993e443ef785e80b9ad2e3e9adf4.tar.bz2
Persist the user's active audio device choice across chromeos session and reboots.
This cl added a new attribute to device's active state settings to describe if the device is made active by user's selection or by automatic priority selection. The device made active by user has a higher priority than the device made active by priority. With the stable device id, the active device selection rule is different than before, which only select the active device by its priority. Please see the details in the following document. https://docs.google.com/a/google.com/document/d/1zmSeM956Njh_9ZdLqyld-NTOpJyXVIfzv8DvQ1Xcksw/edit?usp=sharing BUG=308143 TBR=stevenjb,rkc,derat Review URL: https://codereview.chromium.org/1746843002 Cr-Commit-Position: refs/heads/master@{#378812}
Diffstat (limited to 'chromeos')
-rw-r--r--chromeos/audio/audio_devices_pref_handler.h30
-rw-r--r--chromeos/audio/audio_devices_pref_handler_impl.cc51
-rw-r--r--chromeos/audio/audio_devices_pref_handler_impl.h9
-rw-r--r--chromeos/audio/audio_devices_pref_handler_impl_unittest.cc40
-rw-r--r--chromeos/audio/audio_devices_pref_handler_stub.cc27
-rw-r--r--chromeos/audio/audio_devices_pref_handler_stub.h18
-rw-r--r--chromeos/audio/cras_audio_handler.cc487
-rw-r--r--chromeos/audio/cras_audio_handler.h93
-rw-r--r--chromeos/audio/cras_audio_handler_unittest.cc267
-rw-r--r--chromeos/dbus/cras_audio_client.cc5
-rw-r--r--chromeos/dbus/cras_audio_client.h7
-rw-r--r--chromeos/dbus/fake_cras_audio_client.cc5
-rw-r--r--chromeos/dbus/fake_cras_audio_client.h2
13 files changed, 793 insertions, 248 deletions
diff --git a/chromeos/audio/audio_devices_pref_handler.h b/chromeos/audio/audio_devices_pref_handler.h
index 7b8d7cc..deba1e6 100644
--- a/chromeos/audio/audio_devices_pref_handler.h
+++ b/chromeos/audio/audio_devices_pref_handler.h
@@ -15,20 +15,6 @@ namespace chromeos {
struct AudioDevice;
-// Used to represent an audio device's state. This state should be updated
-// to AUDIO_STATE_ACTIVE when it is selected as active or set to
-// AUDIO_STATE_INACTIVE when selected to a different device. When the audio
-// device is unplugged, its last active/inactive state should be stored.
-// The default value of device state will be AUDIO_STATE_NOT_AVAILABLE if
-// the device is not found in the pref settings.
-// Note that these states enum can't be renumbered or it would break existing
-// preference.
-enum AudioDeviceState {
- AUDIO_STATE_ACTIVE = 0,
- AUDIO_STATE_INACTIVE = 1,
- AUDIO_STATE_NOT_AVAILABLE = 2,
-};
-
// Interface that handles audio preference related work, reads and writes
// audio preferences, and notifies AudioPrefObserver for audio preference
// changes.
@@ -52,9 +38,19 @@ class CHROMEOS_EXPORT AudioDevicesPrefHandler
// Sets the audio mute value to prefs for a device.
virtual void SetMuteValue(const AudioDevice& device, bool mute_on) = 0;
- virtual AudioDeviceState GetDeviceState(const AudioDevice& device) = 0;
- virtual void SetDeviceState(const AudioDevice& device,
- AudioDeviceState state) = 0;
+ // Sets the device active state in prefs.
+ // Note: |activate_by_user| indicates whether |device| is set to active
+ // by user or by priority, and it only matters when |active| is true.
+ virtual void SetDeviceActive(const AudioDevice& device,
+ bool active,
+ bool activate_by_user) = 0;
+ // Returns false if it fails to get device active state from prefs.
+ // Otherwise, returns true, pass the active state data in |*active|
+ // and |*activate_by_user|.
+ // Note: |*activate_by_user| only matters when |*active| is true.
+ virtual bool GetDeviceActive(const AudioDevice& device,
+ bool* active,
+ bool* activate_by_user) = 0;
// Reads the audio output allowed value from prefs.
virtual bool GetAudioOutputAllowedValue() = 0;
diff --git a/chromeos/audio/audio_devices_pref_handler_impl.cc b/chromeos/audio/audio_devices_pref_handler_impl.cc
index 61b2fc5..968fb6a 100644
--- a/chromeos/audio/audio_devices_pref_handler_impl.cc
+++ b/chromeos/audio/audio_devices_pref_handler_impl.cc
@@ -85,24 +85,43 @@ void AudioDevicesPrefHandlerImpl::SetMuteValue(const AudioDevice& device,
SaveDevicesMutePref();
}
-AudioDeviceState AudioDevicesPrefHandlerImpl::GetDeviceState(
- const AudioDevice& device) {
- std::string device_id_str = GetDeviceIdString(device);
- if (!device_state_settings_->HasKey(device_id_str)) {
- device_state_settings_->SetInteger(
- device_id_str, static_cast<int>(AUDIO_STATE_NOT_AVAILABLE));
- SaveDevicesStatePref();
- }
- int state = static_cast<int>(AUDIO_STATE_NOT_AVAILABLE);
- device_state_settings_->GetInteger(device_id_str, &state);
- return (AudioDeviceState)state;
+void AudioDevicesPrefHandlerImpl::SetDeviceActive(const AudioDevice& device,
+ bool active,
+ bool activate_by_user) {
+ scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
+ dict->SetBoolean("active", active);
+ if (active)
+ dict->SetBoolean("activate_by_user", activate_by_user);
+
+ device_state_settings_->Set(GetDeviceIdString(device), std::move(dict));
+ SaveDevicesStatePref();
}
-void AudioDevicesPrefHandlerImpl::SetDeviceState(const AudioDevice& device,
- AudioDeviceState state) {
- device_state_settings_->SetInteger(GetDeviceIdString(device),
- static_cast<int>(state));
- SaveDevicesStatePref();
+bool AudioDevicesPrefHandlerImpl::GetDeviceActive(const AudioDevice& device,
+ bool* active,
+ bool* activate_by_user) {
+ const std::string device_id_str = GetDeviceIdString(device);
+ if (!device_state_settings_->HasKey(device_id_str))
+ return false;
+
+ base::DictionaryValue* dict = NULL;
+ if (!device_state_settings_->GetDictionary(device_id_str, &dict)) {
+ LOG(ERROR) << "Could not get device state for device:" << device.ToString();
+ return false;
+ }
+ if (!dict->GetBoolean("active", active)) {
+ LOG(ERROR) << "Could not get active value for device:" << device.ToString();
+ return false;
+ }
+
+ if (*active && !dict->GetBoolean("activate_by_user", activate_by_user)) {
+ LOG(ERROR) << "Could not get activate_by_user value for previously "
+ "active device:"
+ << device.ToString();
+ return false;
+ }
+
+ return true;
}
bool AudioDevicesPrefHandlerImpl::GetAudioOutputAllowedValue() {
diff --git a/chromeos/audio/audio_devices_pref_handler_impl.h b/chromeos/audio/audio_devices_pref_handler_impl.h
index 7e30e59..f9090a5 100644
--- a/chromeos/audio/audio_devices_pref_handler_impl.h
+++ b/chromeos/audio/audio_devices_pref_handler_impl.h
@@ -35,9 +35,12 @@ class CHROMEOS_EXPORT AudioDevicesPrefHandlerImpl
bool GetMuteValue(const AudioDevice& device) override;
void SetMuteValue(const AudioDevice& device, bool mute_on) override;
- AudioDeviceState GetDeviceState(const AudioDevice& device) override;
- void SetDeviceState(const AudioDevice& device,
- AudioDeviceState state) override;
+ void SetDeviceActive(const AudioDevice& device,
+ bool active,
+ bool activate_by_user) override;
+ bool GetDeviceActive(const AudioDevice& device,
+ bool* active,
+ bool* activate_by_user) override;
bool GetAudioOutputAllowedValue() override;
diff --git a/chromeos/audio/audio_devices_pref_handler_impl_unittest.cc b/chromeos/audio/audio_devices_pref_handler_impl_unittest.cc
index c0ffe80..c4f5d51 100644
--- a/chromeos/audio/audio_devices_pref_handler_impl_unittest.cc
+++ b/chromeos/audio/audio_devices_pref_handler_impl_unittest.cc
@@ -97,12 +97,13 @@ TEST_F(AudioDevicesPrefHandlerTest, TestDefaultValues) {
EXPECT_EQ(75.0, audio_pref_handler_->GetInputGainValue(&kInternalMic));
EXPECT_EQ(75.0, audio_pref_handler_->GetOutputVolumeValue(&kHeadphone));
EXPECT_EQ(75.0, audio_pref_handler_->GetOutputVolumeValue(&kHDMIOutput));
- EXPECT_EQ(AUDIO_STATE_NOT_AVAILABLE,
- audio_pref_handler_->GetDeviceState(kInternalMic));
- EXPECT_EQ(AUDIO_STATE_NOT_AVAILABLE,
- audio_pref_handler_->GetDeviceState(kHeadphone));
- EXPECT_EQ(AUDIO_STATE_NOT_AVAILABLE,
- audio_pref_handler_->GetDeviceState(kHDMIOutput));
+ bool active, activate_by_user;
+ EXPECT_FALSE(audio_pref_handler_->GetDeviceActive(kInternalMic, &active,
+ &activate_by_user));
+ EXPECT_FALSE(audio_pref_handler_->GetDeviceActive(kHeadphone, &active,
+ &activate_by_user));
+ EXPECT_FALSE(audio_pref_handler_->GetDeviceActive(kHDMIOutput, &active,
+ &activate_by_user));
}
TEST_F(AudioDevicesPrefHandlerTest, PrefsRegistered) {
@@ -135,15 +136,24 @@ TEST_F(AudioDevicesPrefHandlerTest, TestSpecialCharactersInDeviceNames) {
}
TEST_F(AudioDevicesPrefHandlerTest, TestDeviceStates) {
- audio_pref_handler_->SetDeviceState(kInternalMic, AUDIO_STATE_NOT_AVAILABLE);
- EXPECT_EQ(AUDIO_STATE_NOT_AVAILABLE,
- audio_pref_handler_->GetDeviceState(kInternalMic));
- audio_pref_handler_->SetDeviceState(kHeadphone, AUDIO_STATE_ACTIVE);
- EXPECT_EQ(AUDIO_STATE_ACTIVE,
- audio_pref_handler_->GetDeviceState(kHeadphone));
- audio_pref_handler_->SetDeviceState(kHDMIOutput, AUDIO_STATE_INACTIVE);
- EXPECT_EQ(AUDIO_STATE_INACTIVE,
- audio_pref_handler_->GetDeviceState(kHDMIOutput));
+ audio_pref_handler_->SetDeviceActive(kInternalMic, true, true);
+ bool active = false;
+ bool activate_by_user = false;
+ EXPECT_TRUE(audio_pref_handler_->GetDeviceActive(kInternalMic, &active,
+ &activate_by_user));
+ EXPECT_TRUE(active);
+ EXPECT_TRUE(activate_by_user);
+
+ audio_pref_handler_->SetDeviceActive(kHeadphone, true, false);
+ EXPECT_TRUE(audio_pref_handler_->GetDeviceActive(kHeadphone, &active,
+ &activate_by_user));
+ EXPECT_TRUE(active);
+ EXPECT_FALSE(activate_by_user);
+
+ audio_pref_handler_->SetDeviceActive(kHDMIOutput, false, false);
+ EXPECT_TRUE(audio_pref_handler_->GetDeviceActive(kHDMIOutput, &active,
+ &activate_by_user));
+ EXPECT_FALSE(active);
}
} // namespace chromeos
diff --git a/chromeos/audio/audio_devices_pref_handler_stub.cc b/chromeos/audio/audio_devices_pref_handler_stub.cc
index 21c2fe1..82a5b4a 100644
--- a/chromeos/audio/audio_devices_pref_handler_stub.cc
+++ b/chromeos/audio/audio_devices_pref_handler_stub.cc
@@ -47,17 +47,26 @@ void AudioDevicesPrefHandlerStub::SetMuteValue(const AudioDevice& device,
audio_device_mute_map_[device.stable_device_id] = mute_on;
}
-AudioDeviceState AudioDevicesPrefHandlerStub::GetDeviceState(
- const AudioDevice& device) {
- if (audio_device_state_map_.find(device.stable_device_id) ==
- audio_device_state_map_.end())
- return AUDIO_STATE_NOT_AVAILABLE;
- return audio_device_state_map_[device.stable_device_id];
+void AudioDevicesPrefHandlerStub::SetDeviceActive(const AudioDevice& device,
+ bool active,
+ bool activate_by_user) {
+ DeviceState state;
+ state.active = active;
+ state.activate_by_user = activate_by_user;
+ audio_device_state_map_[device.stable_device_id] = state;
}
-void AudioDevicesPrefHandlerStub::SetDeviceState(const AudioDevice& device,
- AudioDeviceState state) {
- audio_device_state_map_[device.stable_device_id] = state;
+bool AudioDevicesPrefHandlerStub::GetDeviceActive(const AudioDevice& device,
+ bool* active,
+ bool* activate_by_user) {
+ if (audio_device_state_map_.find(device.stable_device_id) ==
+ audio_device_state_map_.end()) {
+ return false;
+ }
+ *active = audio_device_state_map_[device.stable_device_id].active;
+ *activate_by_user =
+ audio_device_state_map_[device.stable_device_id].activate_by_user;
+ return true;
}
bool AudioDevicesPrefHandlerStub::GetAudioOutputAllowedValue() {
diff --git a/chromeos/audio/audio_devices_pref_handler_stub.h b/chromeos/audio/audio_devices_pref_handler_stub.h
index 508cd27..dd5dd52 100644
--- a/chromeos/audio/audio_devices_pref_handler_stub.h
+++ b/chromeos/audio/audio_devices_pref_handler_stub.h
@@ -18,9 +18,14 @@ namespace chromeos {
class CHROMEOS_EXPORT AudioDevicesPrefHandlerStub
: public AudioDevicesPrefHandler {
public:
+ struct DeviceState {
+ bool active;
+ bool activate_by_user;
+ };
+
using AudioDeviceMute = std::map<uint64_t, bool>;
using AudioDeviceVolumeGain = std::map<uint64_t, int>;
- using AudioDeviceLastState = std::map<uint64_t, AudioDeviceState>;
+ using AudioDeviceStateMap = std::map<uint64_t, DeviceState>;
AudioDevicesPrefHandlerStub();
@@ -30,9 +35,12 @@ class CHROMEOS_EXPORT AudioDevicesPrefHandlerStub
void SetVolumeGainValue(const AudioDevice& device, double value) override;
bool GetMuteValue(const AudioDevice& device) override;
void SetMuteValue(const AudioDevice& device, bool mute_on) override;
- AudioDeviceState GetDeviceState(const AudioDevice& device) override;
- void SetDeviceState(const AudioDevice& device,
- AudioDeviceState state) override;
+ void SetDeviceActive(const AudioDevice& device,
+ bool active,
+ bool activate_by_user) override;
+ bool GetDeviceActive(const AudioDevice& device,
+ bool* active,
+ bool* activate_by_user) override;
bool GetAudioOutputAllowedValue() override;
void AddAudioPrefObserver(AudioPrefObserver* observer) override;
void RemoveAudioPrefObserver(AudioPrefObserver* observer) override;
@@ -43,7 +51,7 @@ class CHROMEOS_EXPORT AudioDevicesPrefHandlerStub
private:
AudioDeviceMute audio_device_mute_map_;
AudioDeviceVolumeGain audio_device_volume_gain_map_;
- AudioDeviceLastState audio_device_state_map_;
+ AudioDeviceStateMap audio_device_state_map_;
DISALLOW_COPY_AND_ASSIGN(AudioDevicesPrefHandlerStub);
};
diff --git a/chromeos/audio/cras_audio_handler.cc b/chromeos/audio/cras_audio_handler.cc
index 531f82e..64114c9 100644
--- a/chromeos/audio/cras_audio_handler.cc
+++ b/chromeos/audio/cras_audio_handler.cc
@@ -13,7 +13,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
-#include "chromeos/audio/audio_devices_pref_handler.h"
+#include "base/sys_info.h"
#include "chromeos/audio/audio_devices_pref_handler_stub.h"
#include "chromeos/dbus/dbus_thread_manager.h"
@@ -37,8 +37,8 @@ const int kHDMIRediscoverGracePeriodDurationInMs = 2000;
static CrasAudioHandler* g_cras_audio_handler = NULL;
bool IsSameAudioDevice(const AudioDevice& a, const AudioDevice& b) {
- return a.id == b.id && a.is_input == b.is_input && a.type == b.type
- && a.device_name == b.device_name;
+ return a.stable_device_id == b.stable_device_id && a.is_input == b.is_input &&
+ a.type == b.type && a.device_name == b.device_name;
}
bool IsInNodeList(uint64_t node_id,
@@ -46,6 +46,14 @@ bool IsInNodeList(uint64_t node_id,
return std::find(id_list.begin(), id_list.end(), node_id) != id_list.end();
}
+bool IsDeviceInList(const AudioDevice& device, const AudioNodeList& node_list) {
+ for (const AudioNode& node : node_list) {
+ if (device.stable_device_id == node.stable_device_id)
+ return true;
+ }
+ return false;
+}
+
} // namespace
CrasAudioHandler::AudioObserver::AudioObserver() {
@@ -233,7 +241,7 @@ void CrasAudioHandler::AddActiveNode(uint64_t node_id, bool notify) {
// If there is no primary active device, set |node_id| to primary active node.
if ((device->is_input && !active_input_node_id_) ||
(!device->is_input && !active_output_node_id_)) {
- SwitchToDevice(*device, notify);
+ SwitchToDevice(*device, notify, ACTIVATE_BY_USER);
return;
}
@@ -388,37 +396,50 @@ void CrasAudioHandler::SetInputMute(bool mute_on) {
OnInputMuteChanged(input_mute_on_));
}
-void CrasAudioHandler::SetActiveOutputNode(uint64_t node_id, bool notify) {
- chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
- SetActiveOutputNode(node_id);
+void CrasAudioHandler::SetActiveDevice(const AudioDevice& active_device,
+ bool notify,
+ DeviceActivateType activate_by) {
+ if (active_device.is_input) {
+ chromeos::DBusThreadManager::Get()
+ ->GetCrasAudioClient()
+ ->SetActiveInputNode(active_device.id);
+ } else {
+ chromeos::DBusThreadManager::Get()
+ ->GetCrasAudioClient()
+ ->SetActiveOutputNode(active_device.id);
+ }
+
if (notify)
- NotifyActiveNodeChanged(false);
+ NotifyActiveNodeChanged(active_device.is_input);
- // Save state for all output nodes.
+ // Save active state for the nodes.
for (AudioDeviceMap::iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
- if (it->second.is_input)
+ const AudioDevice& device = it->second;
+ if (device.is_input != active_device.is_input)
continue;
- audio_pref_handler_->SetDeviceState(it->second, it->second.active
- ? AUDIO_STATE_ACTIVE
- : AUDIO_STATE_INACTIVE);
+ SaveDeviceState(device, device.active, activate_by);
}
}
-void CrasAudioHandler::SetActiveInputNode(uint64_t node_id, bool notify) {
- chromeos::DBusThreadManager::Get()->GetCrasAudioClient()->
- SetActiveInputNode(node_id);
- if (notify)
- NotifyActiveNodeChanged(true);
-
- // Save state for all input nodes.
- for (AudioDeviceMap::iterator it = audio_devices_.begin();
- it != audio_devices_.end(); ++it) {
- if (!it->second.is_input)
- continue;
- audio_pref_handler_->SetDeviceState(it->second, it->second.active
- ? AUDIO_STATE_ACTIVE
- : AUDIO_STATE_INACTIVE);
+void CrasAudioHandler::SaveDeviceState(const AudioDevice& device,
+ bool active,
+ DeviceActivateType activate_by) {
+ if (!active) {
+ audio_pref_handler_->SetDeviceActive(device, false, false);
+ } else {
+ switch (activate_by) {
+ case ACTIVATE_BY_USER:
+ audio_pref_handler_->SetDeviceActive(device, true, true);
+ break;
+ case ACTIVATE_BY_PRIORITY:
+ audio_pref_handler_->SetDeviceActive(device, true, false);
+ break;
+ default:
+ // The device is made active due to its previous active state in prefs,
+ // don't change its active state settings in prefs.
+ break;
+ }
}
}
@@ -532,8 +553,8 @@ void CrasAudioHandler::AudioClientRestarted() {
}
void CrasAudioHandler::NodesChanged() {
- // Refresh audio nodes data.
- GetNodes();
+ if (cras_service_available_)
+ GetNodes();
}
void CrasAudioHandler::ActiveOutputNodeChanged(uint64_t node_id) {
@@ -582,6 +603,16 @@ const AudioDevice* CrasAudioHandler::GetDeviceFromId(uint64_t device_id) const {
return &(it->second);
}
+const AudioDevice* CrasAudioHandler::GetDeviceFromStableDeviceId(
+ uint64_t stable_device_id) const {
+ for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
+ it != audio_devices_.end(); ++it) {
+ if (it->second.stable_device_id == stable_device_id)
+ return &(it->second);
+ }
+ return NULL;
+}
+
const AudioDevice* CrasAudioHandler::GetKeyboardMic() const {
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); it++) {
@@ -655,6 +686,25 @@ void CrasAudioHandler::SetupAdditionalActiveAudioNodeState(uint64_t node_id) {
void CrasAudioHandler::InitializeAudioState() {
ApplyAudioPolicy();
+
+ // Defer querying cras for GetNodes until cras service becomes available.
+ cras_service_available_ = false;
+ chromeos::DBusThreadManager::Get()
+ ->GetCrasAudioClient()
+ ->WaitForServiceToBeAvailable(base::Bind(
+ &CrasAudioHandler::InitializeAudioAfterCrasServiceAvailable,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void CrasAudioHandler::InitializeAudioAfterCrasServiceAvailable(
+ bool service_is_available) {
+ if (!service_is_available) {
+ LOG(ERROR) << "Cras service is not available";
+ cras_service_available_ = false;
+ return;
+ }
+
+ cras_service_available_ = true;
GetNodes();
}
@@ -750,62 +800,76 @@ void CrasAudioHandler::GetNodes() {
weak_ptr_factory_.GetWeakPtr()));
}
-bool CrasAudioHandler::ChangeActiveDevice(const AudioDevice& new_active_device,
- uint64_t* current_active_node_id) {
+bool CrasAudioHandler::ChangeActiveDevice(
+ const AudioDevice& new_active_device) {
+ uint64_t& current_active_node_id = new_active_device.is_input
+ ? active_input_node_id_
+ : active_output_node_id_;
// If the device we want to switch to is already the current active device,
// do nothing.
if (new_active_device.active &&
- new_active_device.id == *current_active_node_id) {
+ new_active_device.id == current_active_node_id) {
return false;
}
+ bool found_new_active_device = false;
// Reset all other input or output devices' active status. The active audio
// device from the previous user session can be remembered by cras, but not
// in chrome. see crbug.com/273271.
for (AudioDeviceMap::iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
if (it->second.is_input == new_active_device.is_input &&
- it->second.id != new_active_device.id)
+ it->second.id != new_active_device.id) {
it->second.active = false;
+ } else if (it->second.is_input == new_active_device.is_input &&
+ it->second.id == new_active_device.id) {
+ found_new_active_device = true;
+ }
+ }
+
+ if (!found_new_active_device) {
+ LOG(ERROR) << "Invalid new active device: " << new_active_device.ToString();
+ return false;
}
// Set the current active input/output device to the new_active_device.
- *current_active_node_id = new_active_device.id;
- audio_devices_[*current_active_node_id].active = true;
+ current_active_node_id = new_active_device.id;
+ audio_devices_[current_active_node_id].active = true;
return true;
}
-bool CrasAudioHandler::NonActiveDeviceUnplugged(size_t old_devices_size,
- size_t new_devices_size,
- uint64_t current_active_node) {
- return (new_devices_size < old_devices_size &&
- GetDeviceFromId(current_active_node));
-}
+void CrasAudioHandler::SwitchToDevice(const AudioDevice& device,
+ bool notify,
+ DeviceActivateType activate_by) {
+ if (!ChangeActiveDevice(device))
+ return;
-void CrasAudioHandler::SwitchToDevice(const AudioDevice& device, bool notify) {
- if (device.is_input) {
- if (!ChangeActiveDevice(device, &active_input_node_id_))
- return;
+ if (device.is_input)
SetupAudioInputState();
- SetActiveInputNode(active_input_node_id_, notify);
- } else {
- if (!ChangeActiveDevice(device, &active_output_node_id_))
- return;
+ else
SetupAudioOutputState();
- SetActiveOutputNode(active_output_node_id_, notify);
- }
+
+ SetActiveDevice(device, notify, activate_by);
}
-bool CrasAudioHandler::HasDeviceChange(
- const AudioNodeList& new_nodes,
- bool is_input,
- AudioDevicePriorityQueue* new_discovered) {
- size_t num_old_devices = 0;
- size_t num_new_devices = 0;
+bool CrasAudioHandler::HasDeviceChange(const AudioNodeList& new_nodes,
+ bool is_input,
+ AudioDevicePriorityQueue* new_discovered,
+ bool* device_removed,
+ bool* active_device_removed) {
+ *device_removed = false;
for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
it != audio_devices_.end(); ++it) {
- if (is_input == it->second.is_input)
- ++num_old_devices;
+ const AudioDevice& device = it->second;
+ if (is_input != device.is_input)
+ continue;
+ if (!IsDeviceInList(device, new_nodes)) {
+ *device_removed = true;
+ if ((is_input && device.id == active_input_node_id_) ||
+ (!is_input && device.id == active_output_node_id_)) {
+ *active_device_removed = true;
+ }
+ }
}
bool new_or_changed_device = false;
@@ -813,31 +877,31 @@ bool CrasAudioHandler::HasDeviceChange(
new_discovered->pop();
for (AudioNodeList::const_iterator it = new_nodes.begin();
it != new_nodes.end(); ++it) {
- if (is_input == it->is_input) {
- ++num_new_devices;
- // Look to see if the new device not in the old device list.
- AudioDevice device(*it);
- DeviceStatus status = CheckDeviceStatus(device);
- if (status == NEW_DEVICE)
- new_discovered->push(device);
- if (status == NEW_DEVICE || status == CHANGED_DEVICE) {
- new_or_changed_device = true;
- }
+ if (is_input != it->is_input)
+ continue;
+ // Check if the new device is not in the old device list.
+ AudioDevice device(*it);
+ DeviceStatus status = CheckDeviceStatus(device);
+ if (status == NEW_DEVICE)
+ new_discovered->push(device);
+ if (status == NEW_DEVICE || status == CHANGED_DEVICE) {
+ new_or_changed_device = true;
}
}
- return new_or_changed_device || (num_old_devices != num_new_devices);
+ return new_or_changed_device || *device_removed;
}
CrasAudioHandler::DeviceStatus CrasAudioHandler::CheckDeviceStatus(
const AudioDevice& device) {
- const AudioDevice* device_found = GetDeviceFromId(device.id);
+ const AudioDevice* device_found =
+ GetDeviceFromStableDeviceId(device.stable_device_id);
if (!device_found)
return NEW_DEVICE;
if (!IsSameAudioDevice(device, *device_found)) {
- LOG(WARNING) << "Different Audio devices with same id:"
- << " new device: " << device.ToString()
- << " old device: " << device_found->ToString();
+ LOG(ERROR) << "Different Audio devices with same stable device id:"
+ << " new device: " << device.ToString()
+ << " old device: " << device_found->ToString();
return CHANGED_DEVICE;
} else if (device.active != device_found->active) {
return CHANGED_DEVICE;
@@ -853,6 +917,150 @@ void CrasAudioHandler::NotifyActiveNodeChanged(bool is_input) {
FOR_EACH_OBSERVER(AudioObserver, observers_, OnActiveOutputNodeChanged());
}
+bool CrasAudioHandler::GetActiveDeviceFromUserPref(bool is_input,
+ AudioDevice* active_device) {
+ bool found_active_device = false;
+ bool last_active_device_activate_by_user = false;
+ for (AudioDeviceMap::const_iterator it = audio_devices_.begin();
+ it != audio_devices_.end(); ++it) {
+ AudioDevice device = it->second;
+ if (device.is_input != is_input)
+ continue;
+
+ bool active = false;
+ bool activate_by_user = false;
+ if (!audio_pref_handler_->GetDeviceActive(device, &active,
+ &activate_by_user) ||
+ !active) {
+ continue;
+ }
+
+ if (!found_active_device) {
+ found_active_device = true;
+ *active_device = device;
+ last_active_device_activate_by_user = activate_by_user;
+ continue;
+ }
+
+ // Choose the best one among multiple active devices from prefs.
+ if (activate_by_user) {
+ if (!last_active_device_activate_by_user) {
+ // Device activated by user has higher priority than the one
+ // is not activated by user.
+ *active_device = device;
+ last_active_device_activate_by_user = true;
+ } else {
+ // If there are more than one active devices activated by user in the
+ // prefs, most likely, after the device was shut down, and before it
+ // is rebooted, user has plugged in some previously unplugged audio
+ // devices. For such case, it does not make sense to honor the active
+ // states in the prefs.
+ VLOG(1) << "Found more than one user activated devices in the prefs.";
+ return false;
+ }
+ } else if (!last_active_device_activate_by_user) {
+ // If there are more than one active devices activated by priority in the
+ // prefs, most likely, cras is still enumerating the audio devices
+ // progressively. For such case, it does not make sense to honor the
+ // active states in the prefs.
+ VLOG(1) << "Found more than one active devices by priority in the prefs.";
+ return false;
+ }
+ }
+
+ if (found_active_device && !active_device->is_for_simple_usage()) {
+ // This is an odd case which is rare but possible to happen during cras
+ // initialization depeneding the audio device enumation process. The only
+ // audio node coming from cras is an internal audio device not visible
+ // to user, such as AUDIO_TYPE_POST_MIX_LOOPBACK.
+ return false;
+ }
+
+ return found_active_device;
+}
+
+void CrasAudioHandler::HandleNonHotplugNodesChange(
+ bool is_input,
+ const AudioDevicePriorityQueue& hotplug_nodes,
+ bool has_device_change,
+ bool has_device_removed,
+ bool active_device_removed) {
+ bool has_current_active_node =
+ is_input ? active_input_node_id_ : active_output_node_id_;
+
+ // No device change, extra NodesChanged signal received.
+ if (!has_device_change && has_current_active_node)
+ return;
+
+ if (hotplug_nodes.empty()) {
+ // Unplugged a non-active device.
+ if (has_device_removed) {
+ if (!active_device_removed && has_current_active_node) {
+ // Removed a non-active device, keep the current active device.
+ return;
+ }
+
+ if (active_device_removed) {
+ // Unplugged the current active device.
+ SwitchToTopPriorityDevice(is_input);
+ return;
+ }
+ }
+
+ // Some unexpected error happens on cras side. See crbug.com/586026.
+ // Either cras sent stale nodes to chrome again or cras triggered some
+ // error. Restore the previously selected active.
+ VLOG(1) << "Odd case from cras, the active node is lost unexpectedly. ";
+ SwitchToPreviousActiveDeviceIfAvailable(is_input);
+ } else {
+ // Looks like a new chrome session starts.
+ SwitchToPreviousActiveDeviceIfAvailable(is_input);
+ }
+}
+
+void CrasAudioHandler::HandleHotPlugDevice(
+ const AudioDevice& hotplug_device,
+ const AudioDevicePriorityQueue& device_priority_queue) {
+ bool last_state_active = false;
+ bool last_activate_by_user = false;
+ if (!audio_pref_handler_->GetDeviceActive(hotplug_device, &last_state_active,
+ &last_activate_by_user)) {
+ // |hotplug_device| is plugged in for the first time, activate it if it
+ // is of the highest priority.
+ if (device_priority_queue.top().id == hotplug_device.id) {
+ VLOG(1) << "Hotplug a device for the first time: "
+ << hotplug_device.ToString();
+ SwitchToDevice(hotplug_device, true, ACTIVATE_BY_PRIORITY);
+ }
+ } else if (last_state_active) {
+ VLOG(1) << "Hotplug a device, restore to active: "
+ << hotplug_device.ToString();
+ SwitchToDevice(hotplug_device, true, ACTIVATE_BY_RESTORE_PREVIOUS_STATE);
+ } else {
+ // Do not active the device if its previous state is inactive.
+ VLOG(1) << "Hotplug device remains inactive as its previous state:"
+ << hotplug_device.ToString();
+ }
+}
+
+void CrasAudioHandler::SwitchToTopPriorityDevice(bool is_input) {
+ AudioDevice top_device =
+ is_input ? input_devices_pq_.top() : output_devices_pq_.top();
+ SwitchToDevice(top_device, true, ACTIVATE_BY_PRIORITY);
+}
+
+void CrasAudioHandler::SwitchToPreviousActiveDeviceIfAvailable(bool is_input) {
+ AudioDevice previous_active_device;
+ if (GetActiveDeviceFromUserPref(is_input, &previous_active_device)) {
+ // Switch to previous active device stored in user prefs.
+ SwitchToDevice(previous_active_device, true,
+ ACTIVATE_BY_RESTORE_PREVIOUS_STATE);
+ } else {
+ // No previous active device, switch to the top priority device.
+ SwitchToTopPriorityDevice(is_input);
+ }
+}
+
void CrasAudioHandler::UpdateDevicesAndSwitchActive(
const AudioNodeList& nodes) {
size_t old_output_device_size = 0;
@@ -867,10 +1075,16 @@ void CrasAudioHandler::UpdateDevicesAndSwitchActive(
AudioDevicePriorityQueue hotplug_output_nodes;
AudioDevicePriorityQueue hotplug_input_nodes;
+ bool has_output_removed = false;
+ bool has_input_removed = false;
+ bool active_output_removed = false;
+ bool active_input_removed = false;
bool output_devices_changed =
- HasDeviceChange(nodes, false, &hotplug_output_nodes);
+ HasDeviceChange(nodes, false, &hotplug_output_nodes, &has_output_removed,
+ &active_output_removed);
bool input_devices_changed =
- HasDeviceChange(nodes, true, &hotplug_input_nodes);
+ HasDeviceChange(nodes, true, &hotplug_input_nodes, &has_input_removed,
+ &active_input_removed);
audio_devices_.clear();
has_alternative_input_ = false;
has_alternative_output_ = false;
@@ -885,7 +1099,6 @@ void CrasAudioHandler::UpdateDevicesAndSwitchActive(
for (size_t i = 0; i < nodes.size(); ++i) {
AudioDevice device(nodes[i]);
audio_devices_[device.id] = device;
-
if (!has_alternative_input_ &&
device.is_input &&
device.type != AUDIO_TYPE_INTERNAL_MIC &&
@@ -906,88 +1119,48 @@ void CrasAudioHandler::UpdateDevicesAndSwitchActive(
}
}
+ // Handle output device changes.
+ HandleAudioDeviceChange(false, output_devices_pq_, hotplug_output_nodes,
+ output_devices_changed, has_output_removed,
+ active_output_removed);
+
+ // Handle input device changes.
+ HandleAudioDeviceChange(true, input_devices_pq_, hotplug_input_nodes,
+ input_devices_changed, has_input_removed,
+ active_input_removed);
+}
+
+void CrasAudioHandler::HandleAudioDeviceChange(
+ bool is_input,
+ const AudioDevicePriorityQueue& devices_pq,
+ const AudioDevicePriorityQueue& hotplug_nodes,
+ bool has_device_change,
+ bool has_device_removed,
+ bool active_device_removed) {
+ uint64_t& active_node_id =
+ is_input ? active_input_node_id_ : active_output_node_id_;
+
+ // No audio devices found.
+ if (devices_pq.empty()) {
+ VLOG(1) << "No " << (is_input ? "input" : "output") << " devices found";
+ active_node_id = 0;
+ NotifyActiveNodeChanged(is_input);
+ return;
+ }
+
// If the previous active device is removed from the new node list,
- // or changed to inactive by cras, reset active_output_node_id_.
+ // or changed to inactive by cras, reset active_node_id.
// See crbug.com/478968.
- const AudioDevice* active_output = GetDeviceFromId(active_output_node_id_);
- if (!active_output || !active_output->active)
- active_output_node_id_ = 0;
- const AudioDevice* active_input = GetDeviceFromId(active_input_node_id_);
- if (!active_input || !active_input->active)
- active_input_node_id_ = 0;
-
- // If audio nodes change is caused by unplugging some non-active audio
- // devices, we wont't change the current active audio devices.
- if (input_devices_changed &&
- !NonActiveDeviceUnplugged(old_input_device_size,
- new_input_device_size,
- active_input_node_id_)) {
- // Some devices like chromeboxes don't have the internal audio input. In
- // that case the active input node id should be reset.
- if (input_devices_pq_.empty()) {
- active_input_node_id_ = 0;
- NotifyActiveNodeChanged(true);
- } else {
- // If there is no current active node, or, no new hot plugged node, select
- // the active node by their priorities.
- if (!active_input_node_id_ || hotplug_input_nodes.empty()) {
- SwitchToDevice(input_devices_pq_.top(), true);
- } else {
- // If user has hot plugged any input nodes, look at the one with highest
- // priority (normally, there is only one hot plugged input node), and
- // consider switch to it depend on its last state stored in preference.
- AudioDeviceState last_state =
- audio_pref_handler_->GetDeviceState(hotplug_input_nodes.top());
- switch (last_state) {
- case AUDIO_STATE_ACTIVE:
- // This node was plugged in before and was selected as the active
- // one
- // before it was unplugged last time, switch to this device.
- SwitchToDevice(hotplug_input_nodes.top(), true);
- break;
- case AUDIO_STATE_NOT_AVAILABLE:
- // This is a new node, not plugged in before, with the highest
- // priority. Switch to this device.
- if (input_devices_pq_.top().id == hotplug_input_nodes.top().id)
- SwitchToDevice(hotplug_input_nodes.top(), true);
- break;
- case AUDIO_STATE_INACTIVE:
- // This node was NOT selected as the active one last time before it
- // got unplugged, so don't switch to it.
- default:
- break;
- }
- }
- }
- }
- if (output_devices_changed &&
- !NonActiveDeviceUnplugged(old_output_device_size,
- new_output_device_size,
- active_output_node_id_)) {
- // ditto input node logic.
- if (output_devices_pq_.empty()) {
- active_output_node_id_ = 0;
- NotifyActiveNodeChanged(false);
- } else {
- if (!active_output_node_id_ || hotplug_output_nodes.empty()) {
- SwitchToDevice(output_devices_pq_.top(), true);
- } else {
- AudioDeviceState last_state =
- audio_pref_handler_->GetDeviceState(hotplug_output_nodes.top());
- switch (last_state) {
- case AUDIO_STATE_ACTIVE:
- SwitchToDevice(hotplug_output_nodes.top(), true);
- break;
- case AUDIO_STATE_NOT_AVAILABLE:
- if (output_devices_pq_.top().id == hotplug_output_nodes.top().id)
- SwitchToDevice(hotplug_output_nodes.top(), true);
- break;
- case AUDIO_STATE_INACTIVE:
- default:
- break;
- }
- }
- }
+ const AudioDevice* active_device = GetDeviceFromId(active_node_id);
+ if (!active_device || !active_device->active)
+ active_node_id = 0;
+
+ if (!active_node_id || hotplug_nodes.empty() || hotplug_nodes.size() > 1) {
+ HandleNonHotplugNodesChange(is_input, hotplug_nodes, has_device_change,
+ has_device_removed, active_device_removed);
+ } else {
+ // Typical user hotplug case.
+ HandleHotPlugDevice(hotplug_nodes.top(), devices_pq);
}
}
diff --git a/chromeos/audio/cras_audio_handler.h b/chromeos/audio/cras_audio_handler.h
index c2e37e8..3d325da 100644
--- a/chromeos/audio/cras_audio_handler.h
+++ b/chromeos/audio/cras_audio_handler.h
@@ -15,6 +15,7 @@
#include "base/observer_list.h"
#include "base/timer/timer.h"
#include "chromeos/audio/audio_device.h"
+#include "chromeos/audio/audio_devices_pref_handler.h"
#include "chromeos/audio/audio_pref_observer.h"
#include "chromeos/dbus/audio_node.h"
#include "chromeos/dbus/cras_audio_client.h"
@@ -71,6 +72,12 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
DISALLOW_COPY_AND_ASSIGN(AudioObserver);
};
+ enum DeviceActivateType {
+ ACTIVATE_BY_PRIORITY = 0,
+ ACTIVATE_BY_USER,
+ ACTIVATE_BY_RESTORE_PREVIOUS_STATE,
+ };
+
// Sets the global instance. Must be called before any calls to Get().
static void Initialize(
scoped_refptr<AudioDevicesPrefHandler> audio_pref_handler);
@@ -167,8 +174,12 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
// Mutes or unmutes audio input device.
virtual void SetInputMute(bool mute_on);
- // Switches active audio device to |device|.
- virtual void SwitchToDevice(const AudioDevice& device, bool notify);
+ // Switches active audio device to |device|. |activate_by| indicates why
+ // the device is switched to active: by user's manual choice, by priority,
+ // or by restoring to its previous active state.
+ virtual void SwitchToDevice(const AudioDevice& device,
+ bool notify,
+ DeviceActivateType activate_by);
// Sets volume/gain level for a device.
virtual void SetVolumeGainPercentForDevice(uint64_t device_id, int value);
@@ -227,10 +238,19 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
// SessionManagerClient::Observer overrides.
void EmitLoginPromptVisibleCalled() override;
- // Sets the active audio output/input node to the node with |node_id|.
+ // Sets the |active_device| to be active.
// If |notify|, notifies Active*NodeChange.
- void SetActiveOutputNode(uint64_t node_id, bool notify);
- void SetActiveInputNode(uint64_t node_id, bool notify);
+ // Saves device active states in prefs. |activate_by| indicates how
+ // the device was activated.
+ void SetActiveDevice(const AudioDevice& active_device,
+ bool notify,
+ DeviceActivateType activate_by);
+
+ // Saves |device|'s state in pref. If |active| is true, |activate_by|
+ // indicates how |device| is activated.
+ void SaveDeviceState(const AudioDevice& device,
+ bool active,
+ DeviceActivateType activate_by);
// Sets up the audio device state based on audio policy and audio settings
// saved in prefs.
@@ -241,12 +261,16 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
void SetupAdditionalActiveAudioNodeState(uint64_t node_id);
const AudioDevice* GetDeviceFromId(uint64_t device_id) const;
+ const AudioDevice* GetDeviceFromStableDeviceId(
+ uint64_t stable_device_id) const;
const AudioDevice* GetKeyboardMic() const;
// Initializes audio state, which should only be called when CrasAudioHandler
// is created or cras audio client is restarted.
void InitializeAudioState();
+ void InitializeAudioAfterCrasServiceAvailable(bool service_is_available);
+
// Applies the audio muting policies whenever the user logs in or policy
// change notification is received.
void ApplyAudioPolicy();
@@ -275,23 +299,21 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
// if needed.
void UpdateDevicesAndSwitchActive(const AudioNodeList& nodes);
- // Returns true if *|current_active_node_id| device is changed to
+ // Returns true if the current active device is changed to
// |new_active_device|.
- bool ChangeActiveDevice(const AudioDevice& new_active_device,
- uint64_t* current_active_node_id);
-
- // Returns true if the audio nodes change is caused by some non-active
- // audio nodes unplugged.
- bool NonActiveDeviceUnplugged(size_t old_devices_size,
- size_t new_device_size,
- uint64_t current_active_node);
-
- // Returns true if there is any device change for for input or output,
- // specified by |is_input|.
- // The new discovered nodes are returned in |new_discovered|.
+ bool ChangeActiveDevice(const AudioDevice& new_active_device);
+
+ // Returns true if there are any device changes for input or output
+ // specified by |is_input|, by comparing |audio_devices_| with |new_nodes|.
+ // Passes the new nodes discovered in *|new_discovered|.
+ // *|device_removed| indicates if any devices have been removed.
+ // *|active_device_removed| indicates if the current active device has been
+ // removed.
bool HasDeviceChange(const AudioNodeList& new_nodes,
bool is_input,
- AudioDevicePriorityQueue* new_discovered);
+ AudioDevicePriorityQueue* new_discovered,
+ bool* device_removed,
+ bool* active_device_removed);
// Handles dbus callback for GetNodes.
void HandleGetNodes(const chromeos::AudioNodeList& node_list, bool success);
@@ -334,6 +356,37 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
void NotifyActiveNodeChanged(bool is_input);
+ // Returns true if it retrieves an active audio device from user preference
+ // among the current |audio_devices_|.
+ bool GetActiveDeviceFromUserPref(bool is_input, AudioDevice* device);
+
+ // Handles either input or output device changes, specified by |is_input|.
+ void HandleAudioDeviceChange(bool is_input,
+ const AudioDevicePriorityQueue& devices_pq,
+ const AudioDevicePriorityQueue& hotplug_nodes,
+ bool has_device_change,
+ bool has_device_removed,
+ bool active_device_removed);
+
+ // Handles non-hotplug nodes change cases.
+ void HandleNonHotplugNodesChange(
+ bool is_input,
+ const AudioDevicePriorityQueue& hotplug_nodes,
+ bool has_device_change,
+ bool has_device_removed,
+ bool active_device_removed);
+
+ // Handles the regular user hotplug case.
+ void HandleHotPlugDevice(
+ const AudioDevice& hotplug_device,
+ const AudioDevicePriorityQueue& device_priority_queue);
+
+ void SwitchToTopPriorityDevice(bool is_input);
+
+ // Switch to previous active device if it is found, otherwise, switch
+ // to the top priority device.
+ void SwitchToPreviousActiveDeviceIfAvailable(bool is_input);
+
scoped_refptr<AudioDevicesPrefHandler> audio_pref_handler_;
base::ObserverList<AudioObserver> observers_;
@@ -362,6 +415,8 @@ class CHROMEOS_EXPORT CrasAudioHandler : public CrasAudioClient::Observer,
int hdmi_rediscover_grace_period_duration_in_ms_;
bool hdmi_rediscovering_;
+ bool cras_service_available_ = false;
+
base::WeakPtrFactory<CrasAudioHandler> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(CrasAudioHandler);
diff --git a/chromeos/audio/cras_audio_handler_unittest.cc b/chromeos/audio/cras_audio_handler_unittest.cc
index de2c1e4..9566065 100644
--- a/chromeos/audio/cras_audio_handler_unittest.cc
+++ b/chromeos/audio/cras_audio_handler_unittest.cc
@@ -15,6 +15,7 @@
#include "base/run_loop.h"
#include "base/thread_task_runner_handle.h"
#include "base/values.h"
+#include "chromeos/audio/audio_devices_pref_handler.h"
#include "chromeos/audio/audio_devices_pref_handler_stub.h"
#include "chromeos/dbus/audio_node.h"
#include "chromeos/dbus/dbus_thread_manager.h"
@@ -324,6 +325,40 @@ class CrasAudioHandlerTest : public testing::Test {
message_loop_.RunUntilIdle();
}
+ // Set up cras audio handlers with |audio_nodes| and set the active state of
+ // |active_device_in_pref| as active and |activate_by_user| in the pref,
+ // and rest of nodes in |audio_nodes_in_pref| as inactive.
+ void SetupCrasAudioHandlerWithActiveNodeInPref(
+ const AudioNodeList& audio_nodes,
+ const AudioNodeList& audio_nodes_in_pref,
+ const AudioDevice& active_device_in_pref,
+ bool activate_by_user) {
+ DBusThreadManager::Initialize();
+ fake_cras_audio_client_ = static_cast<FakeCrasAudioClient*>(
+ DBusThreadManager::Get()->GetCrasAudioClient());
+ audio_pref_handler_ = new AudioDevicesPrefHandlerStub();
+ bool active;
+ for (const AudioNode& node : audio_nodes_in_pref) {
+ active = node.id == active_device_in_pref.id;
+ audio_pref_handler_->SetDeviceActive(AudioDevice(node), active,
+ activate_by_user);
+ }
+
+ bool activate_by;
+ EXPECT_TRUE(audio_pref_handler_->GetDeviceActive(active_device_in_pref,
+ &active, &activate_by));
+ EXPECT_TRUE(active);
+ EXPECT_EQ(activate_by, activate_by_user);
+
+ fake_cras_audio_client_->SetAudioNodesForTesting(audio_nodes);
+ CrasAudioHandler::Initialize(audio_pref_handler_);
+
+ cras_audio_handler_ = CrasAudioHandler::Get();
+ test_observer_.reset(new TestObserver);
+ cras_audio_handler_->AddAudioObserver(test_observer_.get());
+ message_loop_.RunUntilIdle();
+ }
+
void SetUpCrasAudioHandlerWithPrimaryActiveNode(
const AudioNodeList& audio_nodes,
const AudioNode& primary_active_node) {
@@ -559,7 +594,8 @@ TEST_F(CrasAudioHandlerTest, SwitchActiveOutputDevice) {
// Switch the active output to internal speaker.
AudioDevice internal_speaker(kInternalSpeaker);
- cras_audio_handler_->SwitchToDevice(internal_speaker, true);
+ cras_audio_handler_->SwitchToDevice(internal_speaker, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
// Verify the active output is switched to internal speaker, and the
// ActiveOutputNodeChanged event is fired.
@@ -586,7 +622,8 @@ TEST_F(CrasAudioHandlerTest, SwitchActiveInputDevice) {
// Switch the active input to internal mic.
AudioDevice internal_mic(kInternalMic);
- cras_audio_handler_->SwitchToDevice(internal_mic, true);
+ cras_audio_handler_->SwitchToDevice(internal_mic, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
// Verify the active output is switched to internal speaker, and the active
// ActiveInputNodeChanged event is fired.
@@ -1140,7 +1177,8 @@ TEST_F(CrasAudioHandlerTest, UnplugUSBHeadphonesWithActiveSpeaker) {
// Select the speaker to be the active output device.
AudioDevice internal_speaker(kInternalSpeaker);
- cras_audio_handler_->SwitchToDevice(internal_speaker, true);
+ cras_audio_handler_->SwitchToDevice(internal_speaker, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
// Verify the active output is switched to internal speaker, and the
// ActiveOutputNodeChanged event is fired.
@@ -1371,7 +1409,8 @@ TEST_F(CrasAudioHandlerTest, PlugUSBMicNotAffectActiveOutput) {
// Switch the active output to internal speaker.
AudioDevice internal_speaker(kInternalSpeaker);
- cras_audio_handler_->SwitchToDevice(internal_speaker, true);
+ cras_audio_handler_->SwitchToDevice(internal_speaker, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
// Verify the active output is switched to internal speaker, and the
// ActiveOutputNodeChanged event is fired.
@@ -2066,7 +2105,8 @@ TEST_F(CrasAudioHandlerTest, ActiveDeviceSelectionWithStableDeviceId) {
// Change the active device to internal speaker, now USB headphone becomes
// inactive.
AudioDevice speaker(kInternalSpeaker);
- cras_audio_handler_->SwitchToDevice(speaker, true);
+ cras_audio_handler_->SwitchToDevice(speaker, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
EXPECT_NE(kUSBHeadphone1.id,
cras_audio_handler_->GetPrimaryActiveOutputNode());
@@ -2112,6 +2152,11 @@ TEST_F(CrasAudioHandlerTest, ActiveDeviceSelectionWithStableDeviceId) {
// by its priority.
EXPECT_EQ(usb_headset.id, cras_audio_handler_->GetPrimaryActiveOutputNode());
+ audio_nodes.clear();
+ internal_speaker.active = false;
+ audio_nodes.push_back(internal_speaker);
+ usb_headset.active = true;
+ audio_nodes.push_back(usb_headset);
usb_headset2.active = false;
usb_headset2.plugged_time = 80000002;
audio_nodes.push_back(usb_headset2);
@@ -2122,6 +2167,212 @@ TEST_F(CrasAudioHandlerTest, ActiveDeviceSelectionWithStableDeviceId) {
EXPECT_EQ(usb_headset2.id, cras_audio_handler_->GetPrimaryActiveOutputNode());
}
+// Test the device new session case, either via reboot or logout, if there
+// is an active device in the previous session, that device should still
+// be set as active after the new session starts.
+TEST_F(CrasAudioHandlerTest, PersistActiveDeviceAcrossSession) {
+ // Set the active device to internal speaker before the session starts.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kHeadphone);
+ SetupCrasAudioHandlerWithActiveNodeInPref(
+ audio_nodes, audio_nodes, AudioDevice(kInternalSpeaker), true);
+
+ // Verify the audio devices size.
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+
+ // Verify the active device is the internal speaker, which is of a lower
+ // priority, but selected as active since it was the active device previously.
+ EXPECT_EQ(kInternalSpeaker.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+}
+
+TEST_F(CrasAudioHandlerTest, PersistActiveSpeakerAcrossReboot) {
+ // Simulates the device was shut down with three audio devices, and
+ // internal speaker being the active one selected by user.
+ AudioNodeList audio_nodes_in_pref;
+ audio_nodes_in_pref.push_back(kInternalSpeaker);
+ audio_nodes_in_pref.push_back(kHeadphone);
+ audio_nodes_in_pref.push_back(kUSBHeadphone1);
+
+ // Simulate the first NodesChanged signal coming with only one node.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kUSBHeadphone1);
+
+ SetupCrasAudioHandlerWithActiveNodeInPref(
+ audio_nodes, audio_nodes_in_pref, AudioDevice(kInternalSpeaker), true);
+
+ // Verify the usb headphone has been made active.
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kUSBHeadphone1.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+ // Simulate another NodesChanged signal coming later with all ndoes.
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kHeadphone);
+ ChangeAudioNodes(audio_nodes);
+
+ // Verify the active output has been restored to internal speaker.
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kInternalSpeaker.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+}
+
+TEST_F(CrasAudioHandlerTest,
+ PersistActiveUsbHeadphoneAcrossRebootUsbComeLater) {
+ // Simulates the device was shut down with three audio devices, and
+ // usb headphone being the active one selected by priority.
+ AudioNodeList audio_nodes_in_pref;
+ audio_nodes_in_pref.push_back(kInternalSpeaker);
+ audio_nodes_in_pref.push_back(kHeadphone);
+ audio_nodes_in_pref.push_back(kUSBHeadphone1);
+
+ // Simulate the first NodesChanged signal coming with only internal speaker
+ // and the headphone.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kHeadphone);
+
+ SetupCrasAudioHandlerWithActiveNodeInPref(audio_nodes, audio_nodes_in_pref,
+ AudioDevice(kUSBHeadphone1), false);
+
+ // Verify the headphone has been made active.
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kHeadphone.id, cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+ // Simulate USB node comes later with all ndoes.
+ AudioNode usb_node(kUSBHeadphone1);
+ usb_node.plugged_time = 80000000;
+ audio_nodes.push_back(usb_node);
+ ChangeAudioNodes(audio_nodes);
+
+ // Verify the active output has been restored to usb headphone.
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kUSBHeadphone1.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+}
+
+TEST_F(CrasAudioHandlerTest,
+ PersistActiveUsbHeadphoneAcrossRebootUsbComeFirst) {
+ // Simulates the device was shut down with three audio devices, and
+ // usb headphone being the active one selected by priority.
+ AudioNodeList audio_nodes_in_pref;
+ audio_nodes_in_pref.push_back(kInternalSpeaker);
+ audio_nodes_in_pref.push_back(kHeadphone);
+ audio_nodes_in_pref.push_back(kUSBHeadphone1);
+
+ // Simulate the first NodesChanged signal coming with only internal speaker
+ // and the USB headphone.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kUSBHeadphone1);
+
+ SetupCrasAudioHandlerWithActiveNodeInPref(audio_nodes, audio_nodes_in_pref,
+ AudioDevice(kUSBHeadphone1), false);
+
+ // Verify the USB headphone has been made active.
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kUSBHeadphone1.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+ // Simulate another NodesChanged signal coming later with all ndoes.
+ AudioNode headphone_node(kHeadphone);
+ headphone_node.plugged_time = 80000000;
+ audio_nodes.push_back(headphone_node);
+ ChangeAudioNodes(audio_nodes);
+
+ // Verify the active output has been restored to USB headphone.
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kUSBHeadphone1.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+}
+
+// This covers the crbug.com/586026. Cras lost the active state of the internal
+// speaker when user unplugs the headphone, which is a bug in cras. However,
+// chrome code is still resilient and set the internal speaker back to active.
+TEST_F(CrasAudioHandlerTest, UnplugHeadphoneLostActiveInternalSpeakerByCras) {
+ // Set up with three nodes.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kHeadphone);
+ audio_nodes.push_back(kUSBHeadphone1);
+ SetUpCrasAudioHandler(audio_nodes);
+
+ // Switch the active output to internal speaker.
+ cras_audio_handler_->SwitchToDevice(AudioDevice(kInternalSpeaker), true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
+
+ // Verify internal speaker has been made active.
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kInternalSpeaker.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+ // Simulate unplug the headphone. Cras sends NodesChanged signal with
+ // both internal speaker and usb headphone being inactive.
+ audio_nodes.clear();
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kUSBHeadphone1);
+ EXPECT_FALSE(kInternalSpeaker.active);
+ EXPECT_FALSE(kUSBHeadphone1.active);
+ ChangeAudioNodes(audio_nodes);
+
+ // Verify the active output is set back to internal speaker.
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kInternalSpeaker.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+}
+
+TEST_F(CrasAudioHandlerTest, RemoveNonActiveDevice) {
+ // Set up with three nodes.
+ AudioNodeList audio_nodes;
+ audio_nodes.push_back(kInternalSpeaker);
+ audio_nodes.push_back(kHeadphone);
+ audio_nodes.push_back(kUSBHeadphone1);
+ SetUpCrasAudioHandler(audio_nodes);
+
+ // Switch the active output to internal speaker.
+ cras_audio_handler_->SwitchToDevice(AudioDevice(kInternalSpeaker), true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
+
+ // Verify internal speaker has been made active.
+ AudioDeviceList audio_devices;
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kInternalSpeaker.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+
+ // Remove headphone, which is an non-active device.
+ audio_nodes.clear();
+ AudioNode speaker(kInternalSpeaker);
+ speaker.active = true;
+ audio_nodes.push_back(speaker);
+ AudioNode usb_headphone(kUSBHeadphone1);
+ usb_headphone.active = false;
+ audio_nodes.push_back(usb_headphone);
+
+ ChangeAudioNodes(audio_nodes);
+
+ // Verify the active output remains as internal speaker.
+ cras_audio_handler_->GetAudioDevices(&audio_devices);
+ EXPECT_EQ(audio_nodes.size(), audio_devices.size());
+ EXPECT_EQ(kInternalSpeaker.id,
+ cras_audio_handler_->GetPrimaryActiveOutputNode());
+}
+
TEST_F(CrasAudioHandlerTest, ChangeActiveNodesHotrodInit) {
AudioNodeList audio_nodes;
audio_nodes.push_back(kHDMIOutput);
@@ -2552,7 +2803,8 @@ TEST_F(CrasAudioHandlerTest, HotPlugHDMINotChangeActiveOutput) {
// Manually set the active output to internal speaker.
AudioDevice internal_output(kInternalSpeaker);
- cras_audio_handler_->SwitchToDevice(internal_output, true);
+ cras_audio_handler_->SwitchToDevice(internal_output, true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
// Verify the active output is switched to internal speaker.
EXPECT_EQ(internal_speaker.id,
@@ -2631,7 +2883,8 @@ TEST_F(CrasAudioHandlerTest, HDMIRemainInactiveAfterSuspendResume) {
EXPECT_EQ(hdmi_output.id, cras_audio_handler_->GetPrimaryActiveOutputNode());
// Manually set the active output to internal speaker.
- cras_audio_handler_->SwitchToDevice(AudioDevice(internal_speaker), true);
+ cras_audio_handler_->SwitchToDevice(AudioDevice(internal_speaker), true,
+ CrasAudioHandler::ACTIVATE_BY_USER);
EXPECT_EQ(internal_speaker.id,
cras_audio_handler_->GetPrimaryActiveOutputNode());
diff --git a/chromeos/dbus/cras_audio_client.cc b/chromeos/dbus/cras_audio_client.cc
index d6c5b71..acb4bf7 100644
--- a/chromeos/dbus/cras_audio_client.cc
+++ b/chromeos/dbus/cras_audio_client.cc
@@ -186,6 +186,11 @@ class CrasAudioClientImpl : public CrasAudioClient {
dbus::ObjectProxy::EmptyResponseCallback());
}
+ void WaitForServiceToBeAvailable(
+ const WaitForServiceToBeAvailableCallback& callback) override {
+ cras_proxy_->WaitForServiceToBeAvailable(callback);
+ }
+
protected:
void Init(dbus::Bus* bus) override {
cras_proxy_ = bus->GetObjectProxy(cras::kCrasServiceName,
diff --git a/chromeos/dbus/cras_audio_client.h b/chromeos/dbus/cras_audio_client.h
index 0eff85d..dd73040 100644
--- a/chromeos/dbus/cras_audio_client.h
+++ b/chromeos/dbus/cras_audio_client.h
@@ -70,6 +70,9 @@ class CHROMEOS_EXPORT CrasAudioClient : public DBusClient {
// contains the detailed dbus error message.
typedef base::Callback<void(const std::string&,
const std::string&)> ErrorCallback;
+ // A callback for cras dbus method WaitForServiceToBeAvailable.
+ typedef base::Callback<void(bool service_is_ready)>
+ WaitForServiceToBeAvailableCallback;
// Gets the volume state, asynchronously.
virtual void GetVolumeState(const GetVolumeStateCallback& callback) = 0;
@@ -125,6 +128,10 @@ class CHROMEOS_EXPORT CrasAudioClient : public DBusClient {
// |node_id|.
virtual void SwapLeftRight(uint64_t node_id, bool swap) = 0;
+ // Runs the callback as soon as the service becomes available.
+ virtual void WaitForServiceToBeAvailable(
+ const WaitForServiceToBeAvailableCallback& callback) = 0;
+
// Creates the instance.
static CrasAudioClient* Create();
diff --git a/chromeos/dbus/fake_cras_audio_client.cc b/chromeos/dbus/fake_cras_audio_client.cc
index 8bffa16..b8778ca 100644
--- a/chromeos/dbus/fake_cras_audio_client.cc
+++ b/chromeos/dbus/fake_cras_audio_client.cc
@@ -174,6 +174,11 @@ void FakeCrasAudioClient::AddActiveOutputNode(uint64_t node_id) {
}
}
+void FakeCrasAudioClient::WaitForServiceToBeAvailable(
+ const WaitForServiceToBeAvailableCallback& callback) {
+ callback.Run(true);
+}
+
void FakeCrasAudioClient::RemoveActiveOutputNode(uint64_t node_id) {
for (size_t i = 0; i < node_list_.size(); ++i) {
if (node_list_[i].id == node_id)
diff --git a/chromeos/dbus/fake_cras_audio_client.h b/chromeos/dbus/fake_cras_audio_client.h
index 7ba80ab..8de816f 100644
--- a/chromeos/dbus/fake_cras_audio_client.h
+++ b/chromeos/dbus/fake_cras_audio_client.h
@@ -40,6 +40,8 @@ class CHROMEOS_EXPORT FakeCrasAudioClient : public CrasAudioClient {
void AddActiveOutputNode(uint64_t node_id) override;
void RemoveActiveOutputNode(uint64_t node_id) override;
void SwapLeftRight(uint64_t node_id, bool swap) override;
+ void WaitForServiceToBeAvailable(
+ const WaitForServiceToBeAvailableCallback& callback) override;
// Modifies an AudioNode from |node_list_| based on |audio_node.id|.
// if the |audio_node.id| cannot be found in list, Add an