// 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 "chromeos/dbus/cras_audio_client_stub_impl.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 {
// Error name if cras dbus call fails with empty ErrorResponse.
const char kNoResponseError[] =
"org.chromium.cras.Error.NoResponse";
// The CrasAudioClient implementation used in production.
class CrasAudioClientImpl : public CrasAudioClient {
public:
CrasAudioClientImpl() : cras_proxy_(NULL), weak_ptr_factory_(this) {}
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,
const ErrorCallback& error_callback) OVERRIDE {
dbus::MethodCall method_call(cras::kCrasControlInterface,
cras::kGetNodes);
cras_proxy_->CallMethodWithErrorCallback(
&method_call,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::Bind(&CrasAudioClientImpl::OnGetNodes,
weak_ptr_factory_.GetWeakPtr(), callback),
base::Bind(&CrasAudioClientImpl::OnError,
weak_ptr_factory_.GetWeakPtr(), error_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());
}
protected:
virtual void Init(dbus::Bus* bus) OVERRIDE {
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()));
}
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(const std::string& old_owner,
const std::string& new_owner) {
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.empty())
return;
callback.Run(node_list, success);
}
void OnError(const ErrorCallback& error_callback,
dbus::ErrorResponse* response) {
// Error response has optional error message argument.
std::string error_name;
std::string error_message;
if (response) {
dbus::MessageReader reader(response);
error_name = response->GetErrorName();
reader.PopString(&error_message);
} else {
error_name = kNoResponseError;
error_message = "";
}
error_callback.Run(error_name, error_message);
}
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<Observer> 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<CrasAudioClientImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(CrasAudioClientImpl);
};
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() {
return new CrasAudioClientImpl();
}
} // namespace chromeos