blob: 8919e52ffbd67e85bf88a0bf0d4733f1b7a8174e [file] [log] [blame]
// 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<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);
};
// 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<Observer> 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