blob: 6b5669534e30843175e50782f396de797f1be677 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS 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 "power_manager/powerd/system/audio_client.h"
#include <algorithm>
#include <map>
#include <memory>
#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/time/time.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/message.h>
#include "power_manager/common/util.h"
#include "power_manager/powerd/system/audio_observer.h"
namespace power_manager {
namespace system {
namespace {
// Maximum amount of time to wait for a reply from CRAS.
constexpr base::TimeDelta kCrasDBusTimeout = base::TimeDelta::FromSeconds(3);
} // namespace
// static
constexpr char AudioClient::kTypeKey[];
constexpr char AudioClient::kActiveKey[];
constexpr char AudioClient::kHeadphoneNodeType[];
constexpr char AudioClient::kHdmiNodeType[];
constexpr char AudioClient::kAudioSuspendedFile[];
AudioClient::AudioClient() : weak_ptr_factory_(this) {}
AudioClient::~AudioClient() {
if (dbus_wrapper_)
dbus_wrapper_->RemoveObserver(this);
}
void AudioClient::Init(DBusWrapperInterface* dbus_wrapper,
const base::FilePath& run_dir) {
DCHECK(dbus_wrapper);
dbus_wrapper_ = dbus_wrapper;
dbus_wrapper_->AddObserver(this);
audio_suspended_path_ = run_dir.Append(kAudioSuspendedFile);
cras_proxy_ = dbus_wrapper_->GetObjectProxy(cras::kCrasServiceName,
cras::kCrasServicePath);
dbus_wrapper_->RegisterForServiceAvailability(
cras_proxy_, base::Bind(&AudioClient::HandleCrasAvailableOrRestarted,
weak_ptr_factory_.GetWeakPtr()));
typedef void (AudioClient::*SignalMethod)(dbus::Signal*);
const std::map<const char*, SignalMethod> kSignalMethods = {
{cras::kNodesChanged, &AudioClient::HandleNodesChangedSignal},
{cras::kActiveOutputNodeChanged,
&AudioClient::HandleActiveOutputNodeChangedSignal},
{cras::kNumberOfActiveStreamsChanged,
&AudioClient::HandleNumberOfActiveStreamsChangedSignal},
{cras::kAudioOutputActiveStateChanged,
&AudioClient::HandleAudioOutputActiveStateChangedSignal},
};
for (const auto& it : kSignalMethods) {
dbus_wrapper_->RegisterForSignal(
cras_proxy_, cras::kCrasControlInterface, it.first,
base::Bind(it.second, weak_ptr_factory_.GetWeakPtr()));
}
if (base::PathExists(audio_suspended_path_))
SetSuspended(false);
}
bool AudioClient::GetHeadphoneJackPlugged() const {
return headphone_jack_plugged_;
}
bool AudioClient::GetHdmiActive() const {
return hdmi_active_;
}
void AudioClient::AddObserver(AudioObserver* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void AudioClient::RemoveObserver(AudioObserver* observer) {
DCHECK(observer);
observers_.RemoveObserver(observer);
}
void AudioClient::SetSuspended(bool suspended) {
dbus::MethodCall method_call(cras::kCrasControlInterface,
cras::kSetSuspendAudio);
dbus::MessageWriter writer(&method_call);
writer.AppendBool(suspended);
dbus_wrapper_->CallMethodSync(cras_proxy_, &method_call, kCrasDBusTimeout);
if (suspended) {
if (base::WriteFile(audio_suspended_path_, nullptr, 0) < 0)
PLOG(ERROR) << "Couldn't create " << audio_suspended_path_.value();
} else {
if (!base::DeleteFile(audio_suspended_path_))
PLOG(ERROR) << "Couldn't delete " << audio_suspended_path_.value();
}
}
void AudioClient::OnDBusNameOwnerChanged(const std::string& service_name,
const std::string& old_owner,
const std::string& new_owner) {
if (service_name == cras::kCrasServiceName && !new_owner.empty()) {
LOG(INFO) << "D-Bus " << service_name << " ownership changed to "
<< new_owner;
HandleCrasAvailableOrRestarted(true);
}
}
bool AudioClient::IsAudioActive() const {
return num_output_streams_ > 0 && output_active_;
}
void AudioClient::CallGetNodes() {
dbus::MethodCall method_call(cras::kCrasControlInterface, cras::kGetNodes);
dbus_wrapper_->CallMethodAsync(
cras_proxy_, &method_call, kCrasDBusTimeout,
base::Bind(&AudioClient::HandleGetNodesResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void AudioClient::HandleGetNodesResponse(dbus::Response* response) {
if (!response)
return;
const bool old_headphone_jack_plugged = headphone_jack_plugged_;
const bool old_hdmi_active = hdmi_active_;
headphone_jack_plugged_ = false;
hdmi_active_ = false;
// At the outer level, there's a dictionary corresponding to each audio node.
dbus::MessageReader response_reader(response);
dbus::MessageReader node_reader(nullptr);
while (response_reader.PopArray(&node_reader)) {
std::string type;
bool active = false;
// Iterate over the dictionary's entries.
dbus::MessageReader property_reader(nullptr);
while (node_reader.PopDictEntry(&property_reader)) {
std::string key;
if (!property_reader.PopString(&key)) {
LOG(WARNING) << "Skipping dictionary entry with non-string key";
continue;
}
if (key == kTypeKey) {
if (!property_reader.PopVariantOfString(&type))
LOG(WARNING) << kTypeKey << " key has non-string value";
} else if (key == kActiveKey) {
if (!property_reader.PopVariantOfBool(&active))
LOG(WARNING) << kActiveKey << " key has non-bool value";
}
}
VLOG(1) << "Saw node: type=" << type << " active=" << active;
// The D-Bus interface doesn't return unplugged nodes.
if (type == kHeadphoneNodeType)
headphone_jack_plugged_ = true;
else if (type == kHdmiNodeType && active)
hdmi_active_ = true;
}
if (headphone_jack_plugged_ != old_headphone_jack_plugged ||
hdmi_active_ != old_hdmi_active) {
LOG(INFO) << "Updated audio devices: headphones "
<< (headphone_jack_plugged_ ? "" : "un") << "plugged, "
<< "HDMI " << (hdmi_active_ ? "" : "in") << "active";
}
}
void AudioClient::CallGetNumberOfActiveOutputStreams() {
dbus::MethodCall method_call(cras::kCrasControlInterface,
cras::kGetNumberOfActiveOutputStreams);
dbus_wrapper_->CallMethodAsync(
cras_proxy_, &method_call, kCrasDBusTimeout,
base::Bind(&AudioClient::HandleGetNumberOfActiveOutputStreamsResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void AudioClient::HandleGetNumberOfActiveOutputStreamsResponse(
dbus::Response* response) {
if (!response)
return;
int num_output_streams = 0;
if (!dbus::MessageReader(response).PopInt32(&num_output_streams)) {
LOG(WARNING) << "Unable to read " << cras::kGetNumberOfActiveOutputStreams
<< " args";
return;
}
VLOG(1) << "Output stream count changed to " << num_output_streams;
UpdateAudioState(num_output_streams, output_active_);
}
void AudioClient::CallIsAudioOutputActive() {
dbus::MethodCall method_call(cras::kCrasControlInterface,
cras::kIsAudioOutputActive);
dbus_wrapper_->CallMethodAsync(
cras_proxy_, &method_call, kCrasDBusTimeout,
base::Bind(&AudioClient::HandleIsAudioOutputActiveResponse,
weak_ptr_factory_.GetWeakPtr()));
}
void AudioClient::HandleIsAudioOutputActiveResponse(dbus::Response* response) {
if (!response)
return;
int32_t output_active = 0;
if (!dbus::MessageReader(response).PopInt32(&output_active)) {
LOG(WARNING) << "Unable to read " << cras::kIsAudioOutputActive << " args";
return;
}
VLOG(1) << "Output-active state is " << output_active;
UpdateAudioState(num_output_streams_, output_active > 0);
}
void AudioClient::HandleCrasAvailableOrRestarted(bool available) {
if (!available) {
LOG(ERROR) << "Failed waiting for CRAS to become available";
return;
}
CallGetNodes();
CallGetNumberOfActiveOutputStreams();
CallIsAudioOutputActive();
}
void AudioClient::HandleNodesChangedSignal(dbus::Signal* signal) {
CallGetNodes();
}
void AudioClient::HandleActiveOutputNodeChangedSignal(dbus::Signal* signal) {
CallGetNodes();
}
void AudioClient::HandleNumberOfActiveStreamsChangedSignal(
dbus::Signal* signal) {
// The signal only contains the total count of streams (i.e. both input and
// output), so we need to call the method to get the output stream count.
CallGetNumberOfActiveOutputStreams();
}
void AudioClient::HandleAudioOutputActiveStateChangedSignal(
dbus::Signal* signal) {
bool output_active = false;
if (!dbus::MessageReader(signal).PopBool(&output_active)) {
LOG(WARNING) << "Failed to read " << cras::kAudioOutputActiveStateChanged
<< " args";
return;
}
VLOG(1) << "Output-active state changed to " << output_active;
UpdateAudioState(num_output_streams_, output_active);
}
void AudioClient::UpdateAudioState(int num_output_streams, bool output_active) {
const bool was_active = IsAudioActive();
num_output_streams_ = num_output_streams;
output_active_ = output_active;
const bool is_active = IsAudioActive();
if (is_active != was_active) {
for (AudioObserver& observer : observers_)
observer.OnAudioStateChange(is_active);
}
}
} // namespace system
} // namespace power_manager