// 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 "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 the D-Bus signal for output volume change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kOutputVolumeChanged, base::Bind(&CrasAudioClientImpl::OutputVolumeChangedReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&CrasAudioClientImpl::SignalConnected, 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 gain change. cras_proxy_->ConnectToSignal( cras::kCrasControlInterface, cras::kInputGainChanged, base::Bind(&CrasAudioClientImpl::InputGainChangedReceived, 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 SetOutputVolume(int32 volume) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetOutputVolume); dbus::MessageWriter writer(&method_call); writer.AppendInt32(volume); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetOutputMute(bool mute_on) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetOutputMute); dbus::MessageWriter writer(&method_call); writer.AppendBool(mute_on); cras_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, dbus::ObjectProxy::EmptyResponseCallback()); } virtual void SetInputGain(int32 input_gain) OVERRIDE { dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kSetInputGain); dbus::MessageWriter writer(&method_call); 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; } // Called when a OutputVolumeChanged signal is received. void OutputVolumeChangedReceived(dbus::Signal* signal) { dbus::MessageReader reader(signal); int32 volume; if (!reader.PopInt32(&volume)) { LOG(ERROR) << "Error reading signal from cras:" << signal->ToString(); } FOR_EACH_OBSERVER(Observer, observers_, OutputVolumeChanged(volume)); } // Called when a OutputMuteChanged signal is received. void OutputMuteChangedReceived(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_, OutputMuteChanged(mute)); } // Called when a InputGainChanged signal is received. void InputGainChangedReceived(dbus::Signal* signal) { dbus::MessageReader reader(signal); int32 gain; if (!reader.PopInt32(&gain)) { LOG(ERROR) << "Error reading signal from cras:" << signal->ToString(); } FOR_EACH_OBSERVER(Observer, observers_, InputGainChanged(gain)); } // 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_mute) || !reader.PopInt32(&volume_state.input_gain) || !reader.PopBool(&volume_state.input_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; } node_list.push_back(node); } } else { 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; } } 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"; } virtual ~CrasAudioClientStubImpl() {} // CrasAudioClient overrides: // TODO(jennyz): Implement the observers and callbacks in the stub for UI // testing. virtual void AddObserver(Observer* observer) OVERRIDE {} virtual void RemoveObserver(Observer* observer) OVERRIDE {} virtual bool HasObserver(Observer* observer) OVERRIDE { return false; } virtual void GetVolumeState(const GetVolumeStateCallback& callback) OVERRIDE { } virtual void GetNodes(const GetNodesCallback& callback)OVERRIDE {} virtual void SetOutputVolume(int32 volume) OVERRIDE {} virtual void SetOutputMute(bool mute_on) OVERRIDE {} virtual void SetInputGain(int32 input_gain) OVERRIDE {} virtual void SetInputMute(bool mute_on) OVERRIDE {} virtual void SetActiveOutputNode(uint64 node_id) OVERRIDE {} virtual void SetActiveInputNode(uint64 node_id) OVERRIDE {} private: DISALLOW_COPY_AND_ASSIGN(CrasAudioClientStubImpl); }; CrasAudioClient::Observer::~Observer() { } void CrasAudioClient::Observer::OutputVolumeChanged(int32 volume) { } void CrasAudioClient::Observer::OutputMuteChanged(bool mute_on) { } void CrasAudioClient::Observer::InputGainChanged(int gain) { } 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