summaryrefslogtreecommitdiffstats
path: root/chromeos
diff options
context:
space:
mode:
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