// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromeos/dbus/cras_audio_client.h" #include "base/bind.h" #include "base/format_macros.h" #include "base/strings/stringprintf.h" #include "dbus/bus.h" #include "dbus/message.h" #include "dbus/object_path.h" #include "dbus/object_proxy.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { // The CrasAudioClient implementation used in production. class CrasAudioClientImpl : public CrasAudioClient { public: explicit CrasAudioClientImpl(dbus::Bus* bus) : cras_proxy_(NULL), weak_ptr_factory_(this) { cras_proxy_ = bus->GetObjectProxy( cras::kCrasServiceName, dbus::ObjectPath(cras::kCrasServicePath)); // Monitor NameOwnerChanged signal. cras_proxy_->SetNameOwnerChangedCallback( base::Bind(&CrasAudioClientImpl::NameOwnerChangedReceived, weak_ptr_factory_.GetWeakPtr())); // Monitor the D-Bus signal for output mute change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kOutputMuteChanged, base::Bind(&CrasAudioClientImpl::OutputMuteChangedReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CrasAudioClientImpl::SignalConnected, weak_ptr_factory_.GetWeakPtr())); // Monitor the D-Bus signal for input mute change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kInputMuteChanged, base::Bind(&CrasAudioClientImpl::InputMuteChangedReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CrasAudioClientImpl::SignalConnected, weak_ptr_factory_.GetWeakPtr())); // Monitor the D-Bus signal for nodes change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kNodesChanged, base::Bind(&CrasAudioClientImpl::NodesChangedReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CrasAudioClientImpl::SignalConnected, weak_ptr_factory_.GetWeakPtr())); // Monitor the D-Bus signal for active output node change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kActiveOutputNodeChanged, base::Bind(&CrasAudioClientImpl::ActiveOutputNodeChangedReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CrasAudioClientImpl::SignalConnected, weak_ptr_factory_.GetWeakPtr())); // Monitor the D-Bus signal for active input node change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kActiveInputNodeChanged, base::Bind(&CrasAudioClientImpl::ActiveInputNodeChangedReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CrasAudioClientImpl::SignalConnected, weak_ptr_factory_.GetWeakPtr())); } virtual ~CrasAudioClientImpl() { } // CrasAudioClient overrides: virtual void AddObserver(Observer* observer) OVERRIDE { observers_.AddObserver(observer); } virtual void RemoveObserver(Observer* observer) OVERRIDE { observers_.RemoveObserver(observer); } virtual bool HasObserver(Observer* observer) OVERRIDE { return observers_.HasObserver(observer); } virtual void GetVolumeState(const GetVolumeStateCallback& callback) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kGetVolumeState); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&CrasAudioClientImpl::OnGetVolumeState, weak_ptr_factory_.GetWeakPtr(), callback)); } virtual void GetNodes(const GetNodesCallback& callback) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kGetNodes); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&CrasAudioClientImpl::OnGetNodes, weak_ptr_factory_.GetWeakPtr(), callback)); } virtual void SetOutputNodeVolume(uint64 node_id, int32 volume) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetOutputNodeVolume); dbus::MessageWriter writer(&method_call); writer.AppendUint64(node_id); writer.AppendInt32(volume); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetOutputUserMute(bool mute_on) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetOutputUserMute); dbus::MessageWriter writer(&method_call); writer.AppendBool(mute_on); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetInputNodeGain(uint64 node_id, int32 input_gain) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetInputNodeGain); dbus::MessageWriter writer(&method_call); writer.AppendUint64(node_id); writer.AppendInt32(input_gain); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetInputMute(bool mute_on) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetInputMute); dbus::MessageWriter writer(&method_call); writer.AppendBool(mute_on); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetActiveOutputNode(uint64 node_id) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetActiveOutputNode); dbus::MessageWriter writer(&method_call); writer.AppendUint64(node_id); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetActiveInputNode(uint64 node_id) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetActiveInputNode); dbus::MessageWriter writer(&method_call); writer.AppendUint64(node_id); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } private: // Called when the cras signal is initially connected. void SignalConnected(const std::string& interface_name, const std::string& signal_name, bool success) { LOG_IF(ERROR, !success) << "Failed to connect to cras signal:" << signal_name; } void NameOwnerChangedReceived(dbus::Signal* signal) { FOR_EACH_OBSERVER(Observer, observers_, AudioClientRestarted()); } // Called when a OutputMuteChanged signal is received. void OutputMuteChangedReceived(dbus::Signal* signal) { // Chrome should always call SetOutputUserMute api to set the output // mute state and monitor user_mute state from OutputMuteChanged signal. dbus::MessageReader reader(signal); bool system_mute, user_mute; if (!reader.PopBool(&system_mute) || !reader.PopBool(&user_mute)) { LOG(ERROR) << "Error reading signal from cras:" << signal->ToString(); } FOR_EACH_OBSERVER(Observer, observers_, OutputMuteChanged(user_mute)); } // Called when a InputMuteChanged signal is received. void InputMuteChangedReceived(dbus::Signal* signal) { dbus::MessageReader reader(signal); bool mute; if (!reader.PopBool(&mute)) { LOG(ERROR) << "Error reading signal from cras:" << signal->ToString(); } FOR_EACH_OBSERVER(Observer, observers_, InputMuteChanged(mute)); } void NodesChangedReceived(dbus::Signal* signal) { FOR_EACH_OBSERVER(Observer, observers_, NodesChanged()); } void ActiveOutputNodeChangedReceived(dbus::Signal* signal) { dbus::MessageReader reader(signal); uint64 node_id; if (!reader.PopUint64(&node_id)) { LOG(ERROR) << "Error reading signal from cras:" << signal->ToString(); } FOR_EACH_OBSERVER(Observer, observers_, ActiveOutputNodeChanged(node_id)); } void ActiveInputNodeChangedReceived(dbus::Signal* signal) { dbus::MessageReader reader(signal); uint64 node_id; if (!reader.PopUint64(&node_id)) { LOG(ERROR) << "Error reading signal from cras:" << signal->ToString(); } FOR_EACH_OBSERVER(Observer, observers_, ActiveInputNodeChanged(node_id)); } void OnGetVolumeState(const GetVolumeStateCallback& callback, dbus::Response* response) { bool success = true; VolumeState volume_state; if (response) { dbus::MessageReader reader(response); if (!reader.PopInt32(&volume_state.output_volume) || !reader.PopBool(&volume_state.output_system_mute) || !reader.PopInt32(&volume_state.input_gain) || !reader.PopBool(&volume_state.input_mute) || !reader.PopBool(&volume_state.output_user_mute)) { success = false; LOG(ERROR) << "Error reading response from cras: " << response->ToString(); } } else { success = false; LOG(ERROR) << "Error calling " << cras::kGetVolumeState; } callback.Run(volume_state, success); } void OnGetNodes(const GetNodesCallback& callback, dbus::Response* response) { bool success = true; AudioNodeList node_list; if (response) { dbus::MessageReader response_reader(response); dbus::MessageReader array_reader(response); while (response_reader.HasMoreData()) { if (!response_reader.PopArray(&array_reader)) { success = false; LOG(ERROR) << "Error reading response from cras: " << response->ToString(); break; } AudioNode node; if (!GetAudioNode(response, &array_reader, &node)) { success = false; LOG(WARNING) << "Error reading audio node data from cras: " << response->ToString(); break; } // Filter out the "UNKNOWN" type of audio devices. if (node.type != "UNKNOWN") node_list.push_back(node); } } if (node_list.size() == 0) { success = false; LOG(ERROR) << "Error calling " << cras::kGetNodes; } callback.Run(node_list, success); } bool GetAudioNode(dbus::Response* response, dbus::MessageReader* array_reader, AudioNode *node) { while (array_reader->HasMoreData()) { dbus::MessageReader dict_entry_reader(response); dbus::MessageReader value_reader(response); std::string key; if (!array_reader->PopDictEntry(&dict_entry_reader) || !dict_entry_reader.PopString(&key) || !dict_entry_reader.PopVariant(&value_reader)) { return false; } if (key == cras::kIsInputProperty) { if (!value_reader.PopBool(&node->is_input)) return false; } else if (key == cras::kIdProperty) { if (!value_reader.PopUint64(&node->id)) return false; } else if (key == cras::kDeviceNameProperty) { if (!value_reader.PopString(&node->device_name)) return false; } else if (key == cras::kTypeProperty) { if (!value_reader.PopString(&node->type)) return false; } else if (key == cras::kNameProperty) { if (!value_reader.PopString(&node->name)) return false; } else if (key == cras::kActiveProperty) { if (!value_reader.PopBool(&node->active)) return false; } else if (key == cras::kPluggedTimeProperty) { if (!value_reader.PopUint64(&node->plugged_time)) return false; } } return true; } dbus::ObjectProxy* cras_proxy_; ObserverList observers_; // Note: This should remain the last member so it'll be destroyed and // invalidate its weak pointers before any other members are destroyed. base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(CrasAudioClientImpl); }; // The CrasAudioClient implementation used on Linux desktop, // which does nothing. class CrasAudioClientStubImpl : public CrasAudioClient { public: CrasAudioClientStubImpl() { VLOG(1) << "CrasAudioClientStubImpl is created"; // Fake audio output nodes. AudioNode node_1; node_1.is_input = false; node_1.id = 10001; node_1.device_name = "Fake Speaker"; node_1.type = "INTERNAL_SPEAKER"; node_1.name = "Speaker"; node_1.active = false; node_list_.push_back(node_1); AudioNode node_2; node_2.is_input = false; node_2.id = 10002; node_2.device_name = "Fake Headphone"; node_2.type = "HEADPHONE"; node_2.name = "Headphone"; node_2.active = true; node_list_.push_back(node_2); active_output_node_id_ = node_2.id; AudioNode node_3; node_3.is_input = false; node_3.id = 10003; node_3.device_name = "Fake Audio Output"; node_3.type = "BLUETOOTH"; node_3.name = "Bluetooth Headphone"; node_3.active = false; node_list_.push_back(node_3); // Fake audio input ndoes AudioNode node_4; node_4.is_input = true; node_4.id = 10004; node_4.device_name = "Fake Internal Mic"; node_4.type = "INTERNAL_MIC"; node_4.name = "Internal Mic"; node_4.active = false; node_list_.push_back(node_4); AudioNode node_5; node_5.is_input = true; node_5.id = 10005; node_5.device_name = "Fake Internal Mic"; node_5.type = "USB"; node_5.name = "USB Mic"; node_5.active = true; node_list_.push_back(node_5); active_input_node_id_ = node_5.id; } virtual ~CrasAudioClientStubImpl() { } // CrasAudioClient overrides: // TODO(jennyz): Implement the observers and callbacks in the stub for UI // testing. virtual void AddObserver(Observer* observer) OVERRIDE { observers_.AddObserver(observer); } virtual void RemoveObserver(Observer* observer) OVERRIDE { observers_.RemoveObserver(observer); } virtual bool HasObserver(Observer* observer) OVERRIDE { return observers_.HasObserver(observer); } virtual void GetVolumeState(const GetVolumeStateCallback& callback) OVERRIDE { callback.Run(volume_state_, true); } virtual void GetNodes(const GetNodesCallback& callback)OVERRIDE { callback.Run(node_list_, true); } virtual void SetOutputNodeVolume(uint64 node_id, int32 volume) OVERRIDE { } virtual void SetOutputUserMute(bool mute_on) OVERRIDE { volume_state_.output_user_mute = mute_on; FOR_EACH_OBSERVER(Observer, observers_, OutputMuteChanged(volume_state_.output_user_mute)); } virtual void SetInputNodeGain(uint64 node_id, int32 input_gain) OVERRIDE { } virtual void SetInputMute(bool mute_on) OVERRIDE { volume_state_.input_mute = mute_on; FOR_EACH_OBSERVER(Observer, observers_, InputMuteChanged(volume_state_.input_mute)); } virtual void SetActiveOutputNode(uint64 node_id) OVERRIDE { if (active_output_node_id_ == node_id) return; for (size_t i = 0; i < node_list_.size(); ++i) { if (node_list_[i].id == active_output_node_id_) node_list_[i].active = false; else if (node_list_[i].id == node_id) node_list_[i].active = true; } active_output_node_id_ = node_id; FOR_EACH_OBSERVER(Observer, observers_, ActiveOutputNodeChanged(node_id)); } virtual void SetActiveInputNode(uint64 node_id) OVERRIDE { if (active_input_node_id_ == node_id) return; for (size_t i = 0; i < node_list_.size(); ++i) { if (node_list_[i].id == active_input_node_id_) node_list_[i].active = false; else if (node_list_[i].id == node_id) node_list_[i].active = true; } active_input_node_id_ = node_id; FOR_EACH_OBSERVER(Observer, observers_, ActiveInputNodeChanged(node_id)); } private: VolumeState volume_state_; AudioNodeList node_list_; uint64 active_input_node_id_; uint64 active_output_node_id_; ObserverList observers_; DISALLOW_COPY_AND_ASSIGN(CrasAudioClientStubImpl); }; CrasAudioClient::Observer::~Observer() { } void CrasAudioClient::Observer::AudioClientRestarted() { } void CrasAudioClient::Observer::OutputMuteChanged(bool mute_on) { } void CrasAudioClient::Observer::InputMuteChanged(bool mute_on) { } void CrasAudioClient::Observer::NodesChanged() { } void CrasAudioClient::Observer::ActiveOutputNodeChanged(uint64 node_id){ } void CrasAudioClient::Observer::ActiveInputNodeChanged(uint64 node_id) { } CrasAudioClient::CrasAudioClient() { } CrasAudioClient::~CrasAudioClient() { } // static CrasAudioClient* CrasAudioClient::Create( DBusClientImplementationType type, dbus::Bus* bus) { if (type == REAL_DBUS_CLIENT_IMPLEMENTATION) { return new CrasAudioClientImpl(bus); } DCHECK_EQ(STUB_DBUS_CLIENT_IMPLEMENTATION, type); return new CrasAudioClientStubImpl(); } } // namespace chromeos