diff options
-rw-r--r-- | ash/system/chromeos/audio/audio_detailed_view.cc | 5 | ||||
-rw-r--r-- | chromeos/audio/audio_devices_pref_handler.h | 30 | ||||
-rw-r--r-- | chromeos/audio/audio_devices_pref_handler_impl.cc | 51 | ||||
-rw-r--r-- | chromeos/audio/audio_devices_pref_handler_impl.h | 9 | ||||
-rw-r--r-- | chromeos/audio/audio_devices_pref_handler_impl_unittest.cc | 40 | ||||
-rw-r--r-- | chromeos/audio/audio_devices_pref_handler_stub.cc | 27 | ||||
-rw-r--r-- | chromeos/audio/audio_devices_pref_handler_stub.h | 18 | ||||
-rw-r--r-- | chromeos/audio/cras_audio_handler.cc | 487 | ||||
-rw-r--r-- | chromeos/audio/cras_audio_handler.h | 93 | ||||
-rw-r--r-- | chromeos/audio/cras_audio_handler_unittest.cc | 267 | ||||
-rw-r--r-- | chromeos/dbus/cras_audio_client.cc | 5 | ||||
-rw-r--r-- | chromeos/dbus/cras_audio_client.h | 7 | ||||
-rw-r--r-- | chromeos/dbus/fake_cras_audio_client.cc | 5 | ||||
-rw-r--r-- | chromeos/dbus/fake_cras_audio_client.h | 2 | ||||
-rw-r--r-- | extensions/browser/api/audio/audio_apitest.cc | 3 | ||||
-rw-r--r-- | extensions/shell/browser/shell_audio_controller_chromeos.cc | 6 |
16 files changed, 802 insertions, 253 deletions
diff --git a/ash/system/chromeos/audio/audio_detailed_view.cc b/ash/system/chromeos/audio/audio_detailed_view.cc index 2e76943..ec960ee 100644 --- a/ash/system/chromeos/audio/audio_detailed_view.cc +++ b/ash/system/chromeos/audio/audio_detailed_view.cc @@ -167,8 +167,9 @@ void AudioDetailedView::OnViewClicked(views::View* sender) { AudioDeviceMap::iterator iter = device_map_.find(sender); if (iter == device_map_.end()) return; - chromeos::AudioDevice& device = iter->second; - CrasAudioHandler::Get()->SwitchToDevice(device, true); + chromeos::AudioDevice device = iter->second; + CrasAudioHandler::Get()->SwitchToDevice(device, true, + CrasAudioHandler::ACTIVATE_BY_USER); } } 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 diff --git a/extensions/browser/api/audio/audio_apitest.cc b/extensions/browser/api/audio/audio_apitest.cc index 6d100059..de089e2 100644 --- a/extensions/browser/api/audio/audio_apitest.cc +++ b/extensions/browser/api/audio/audio_apitest.cc @@ -209,7 +209,8 @@ IN_PROC_BROWSER_TEST_F(AudioApiTest, OnInputMuteChanged) { // Set the jabra mic to be the active input device. AudioDevice jabra_mic(kJabraMic1); - cras_audio_handler_->SwitchToDevice(jabra_mic, true); + cras_audio_handler_->SwitchToDevice( + jabra_mic, true, chromeos::CrasAudioHandler::ACTIVATE_BY_USER); EXPECT_EQ(kJabraMic1.id, cras_audio_handler_->GetPrimaryActiveInputNode()); // Un-mute the input. diff --git a/extensions/shell/browser/shell_audio_controller_chromeos.cc b/extensions/shell/browser/shell_audio_controller_chromeos.cc index 19e34ce2..1e89065 100644 --- a/extensions/shell/browser/shell_audio_controller_chromeos.cc +++ b/extensions/shell/browser/shell_audio_controller_chromeos.cc @@ -78,13 +78,15 @@ void ShellAudioController::ActivateDevices() { const chromeos::AudioDevice* device = GetDevice(devices, best_input); DCHECK(device); VLOG(1) << "Activating input device: " << device->ToString(); - handler->SwitchToDevice(*device, true); + handler->SwitchToDevice(*device, true, + chromeos::CrasAudioHandler::ACTIVATE_BY_USER); } if (best_output && best_output != handler->GetPrimaryActiveOutputNode()) { const chromeos::AudioDevice* device = GetDevice(devices, best_output); DCHECK(device); VLOG(1) << "Activating output device: " << device->ToString(); - handler->SwitchToDevice(*device, true); + handler->SwitchToDevice(*device, true, + chromeos::CrasAudioHandler::ACTIVATE_BY_USER); } } |