diff options
Diffstat (limited to 'device/bluetooth/bluetooth_audio_sink_chromeos.cc')
-rw-r--r-- | device/bluetooth/bluetooth_audio_sink_chromeos.cc | 575 |
1 files changed, 575 insertions, 0 deletions
diff --git a/device/bluetooth/bluetooth_audio_sink_chromeos.cc b/device/bluetooth/bluetooth_audio_sink_chromeos.cc new file mode 100644 index 0000000..9bb4b60 --- /dev/null +++ b/device/bluetooth/bluetooth_audio_sink_chromeos.cc @@ -0,0 +1,575 @@ +// Copyright 2015 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 "device/bluetooth/bluetooth_audio_sink_chromeos.h" + +#include <unistd.h> + +#include <algorithm> +#include <sstream> +#include <string> +#include <vector> + +#include "base/debug/stack_trace.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "dbus/message.h" +#include "device/bluetooth/bluetooth_adapter_chromeos.h" +#include "device/bluetooth/dbus/bluez_dbus_manager.h" + +using dbus::ObjectPath; +using device::BluetoothAudioSink; + +namespace { + +// TODO(mcchou): Add the constant to dbus/service_constants.h. +const char kBluetoothAudioSinkServicePath[] = "/org/chromium/AudioSink"; + +const int kInvalidFd = -1; +const uint16_t kInvalidReadMtu = 0; +const uint16_t kInvalidWriteMtu = 0; + +ObjectPath GenerateEndpointPath() { + static unsigned int sequence_number = 0; + ++sequence_number; + std::stringstream path; + path << kBluetoothAudioSinkServicePath << "/endpoint" << sequence_number; + return ObjectPath(path.str()); +} + +std::string StateToString(const BluetoothAudioSink::State& state) { + switch (state) { + case BluetoothAudioSink::STATE_INVALID: + return "invalid"; + case BluetoothAudioSink::STATE_DISCONNECTED: + return "disconnected"; + case BluetoothAudioSink::STATE_IDLE: + return "idle"; + case BluetoothAudioSink::STATE_PENDING: + return "pending"; + case BluetoothAudioSink::STATE_ACTIVE: + return "active"; + default: + return "unknown"; + } +} + +std::string ErrorCodeToString(const BluetoothAudioSink::ErrorCode& error_code) { + switch (error_code) { + case BluetoothAudioSink::ERROR_UNSUPPORTED_PLATFORM: + return "unsupported platform"; + case BluetoothAudioSink::ERROR_INVALID_ADAPTER: + return "invalid adapter"; + case BluetoothAudioSink::ERROR_NOT_REGISTERED: + return "not registered"; + case BluetoothAudioSink::ERROR_NOT_UNREGISTERED: + return "not unregistered"; + default: + return "unknown"; + } +} + +// A dummy error callback for calling Unregister() in destructor. +void UnregisterErrorCallback( + device::BluetoothAudioSink::ErrorCode error_code) { + VLOG(1) << "UnregisterErrorCallback - " << ErrorCodeToString(error_code) + << "(" << error_code << ")"; +} + +} // namespace + +namespace chromeos { + +BluetoothAudioSinkChromeOS::BluetoothAudioSinkChromeOS( + scoped_refptr<device::BluetoothAdapter> adapter) + : state_(BluetoothAudioSink::STATE_INVALID), + volume_(BluetoothAudioSink::kInvalidVolume), + read_mtu_(kInvalidReadMtu), + write_mtu_(kInvalidWriteMtu), + read_has_failed_(false), + adapter_(adapter), + weak_ptr_factory_(this) { + VLOG(1) << "BluetoothAudioSinkChromeOS created"; + + CHECK(adapter_.get()); + CHECK(adapter_->IsPresent()); + CHECK(bluez::BluezDBusManager::IsInitialized()); + + adapter_->AddObserver(this); + + bluez::BluetoothMediaClient* media = + bluez::BluezDBusManager::Get()->GetBluetoothMediaClient(); + CHECK(media); + media->AddObserver(this); + + bluez::BluetoothMediaTransportClient* transport = + bluez::BluezDBusManager::Get()->GetBluetoothMediaTransportClient(); + CHECK(transport); + transport->AddObserver(this); + + StateChanged(device::BluetoothAudioSink::STATE_DISCONNECTED); +} + +BluetoothAudioSinkChromeOS::~BluetoothAudioSinkChromeOS() { + VLOG(1) << "BluetoothAudioSinkChromeOS destroyed"; + + DCHECK(adapter_.get()); + + if (state_ != BluetoothAudioSink::STATE_INVALID && media_endpoint_.get()) { + Unregister(base::Bind(&base::DoNothing), + base::Bind(&UnregisterErrorCallback)); + } + + adapter_->RemoveObserver(this); + + bluez::BluetoothMediaClient* media = + bluez::BluezDBusManager::Get()->GetBluetoothMediaClient(); + CHECK(media); + media->RemoveObserver(this); + + bluez::BluetoothMediaTransportClient* transport = + bluez::BluezDBusManager::Get()->GetBluetoothMediaTransportClient(); + CHECK(transport); + transport->RemoveObserver(this); +} + +void BluetoothAudioSinkChromeOS::Unregister( + const base::Closure& callback, + const device::BluetoothAudioSink::ErrorCallback& error_callback) { + VLOG(1) << "Unregister"; + + if (!bluez::BluezDBusManager::IsInitialized()) + error_callback.Run(BluetoothAudioSink::ERROR_NOT_UNREGISTERED); + + bluez::BluetoothMediaClient* media = + bluez::BluezDBusManager::Get()->GetBluetoothMediaClient(); + CHECK(media); + + media->UnregisterEndpoint( + media_path_, + endpoint_path_, + base::Bind(&BluetoothAudioSinkChromeOS::OnUnregisterSucceeded, + weak_ptr_factory_.GetWeakPtr(), callback), + base::Bind(&BluetoothAudioSinkChromeOS::OnUnregisterFailed, + weak_ptr_factory_.GetWeakPtr(), error_callback)); +} + +void BluetoothAudioSinkChromeOS::AddObserver( + BluetoothAudioSink::Observer* observer) { + CHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothAudioSinkChromeOS::RemoveObserver( + BluetoothAudioSink::Observer* observer) { + CHECK(observer); + observers_.RemoveObserver(observer); +} + +BluetoothAudioSink::State BluetoothAudioSinkChromeOS::GetState() const { + return state_; +} + +uint16_t BluetoothAudioSinkChromeOS::GetVolume() const { + return volume_; +} + +void BluetoothAudioSinkChromeOS::Register( + const BluetoothAudioSink::Options& options, + const base::Closure& callback, + const BluetoothAudioSink::ErrorCallback& error_callback) { + VLOG(1) << "Register"; + + DCHECK(adapter_.get()); + DCHECK_EQ(state_, BluetoothAudioSink::STATE_DISCONNECTED); + + // Gets system bus. + dbus::Bus* system_bus = bluez::BluezDBusManager::Get()->GetSystemBus(); + + // Creates a Media Endpoint with newly-generated path. + endpoint_path_ = GenerateEndpointPath(); + media_endpoint_.reset(bluez::BluetoothMediaEndpointServiceProvider::Create( + system_bus, endpoint_path_, this)); + + DCHECK(media_endpoint_.get()); + + // Creates endpoint properties with |options|. + options_ = options; + bluez::BluetoothMediaClient::EndpointProperties endpoint_properties; + endpoint_properties.uuid = + bluez::BluetoothMediaClient::kBluetoothAudioSinkUUID; + endpoint_properties.codec = options_.codec; + endpoint_properties.capabilities = options_.capabilities; + + media_path_ = static_cast<BluetoothAdapterChromeOS*>( + adapter_.get())->object_path(); + + bluez::BluetoothMediaClient* media = + bluez::BluezDBusManager::Get()->GetBluetoothMediaClient(); + CHECK(media); + media->RegisterEndpoint( + media_path_, + endpoint_path_, + endpoint_properties, + base::Bind(&BluetoothAudioSinkChromeOS::OnRegisterSucceeded, + weak_ptr_factory_.GetWeakPtr(), callback), + base::Bind(&BluetoothAudioSinkChromeOS::OnRegisterFailed, + weak_ptr_factory_.GetWeakPtr(), error_callback)); +} + +bluez::BluetoothMediaEndpointServiceProvider* +BluetoothAudioSinkChromeOS::GetEndpointServiceProvider() { + return media_endpoint_.get(); +} + +void BluetoothAudioSinkChromeOS::AdapterPresentChanged( + device::BluetoothAdapter* adapter, bool present) { + VLOG(1) << "AdapterPresentChanged: " << present; + + if (adapter != adapter_.get()) + return; + + if (adapter->IsPresent()) { + StateChanged(BluetoothAudioSink::STATE_DISCONNECTED); + } else { + adapter_->RemoveObserver(this); + StateChanged(BluetoothAudioSink::STATE_INVALID); + } +} + +void BluetoothAudioSinkChromeOS::AdapterPoweredChanged( + device::BluetoothAdapter* adapter, bool powered) { + VLOG(1) << "AdapterPoweredChanged: " << powered; + + if (adapter != adapter_.get()) + return; + + // Regardless of the new powered state, |state_| goes to STATE_DISCONNECTED. + // If false, the transport is closed, but the endpoint is still valid for use. + // If true, the previous transport has been torn down, so the |state_| has to + // be disconnected before SetConfigruation is called. + if (state_ != BluetoothAudioSink::STATE_INVALID) + StateChanged(BluetoothAudioSink::STATE_DISCONNECTED); +} + +void BluetoothAudioSinkChromeOS::MediaRemoved(const ObjectPath& object_path) { + if (object_path == media_path_) { + VLOG(1) << "MediaRemoved: " << object_path.value(); + StateChanged(BluetoothAudioSink::STATE_INVALID); + } +} + +void BluetoothAudioSinkChromeOS::MediaTransportRemoved( + const ObjectPath& object_path) { + // Whenever powered of |adapter_| turns false while present stays true, media + // transport object should be removed accordingly, and the state should be + // changed to STATE_DISCONNECTED. + if (object_path == transport_path_) { + VLOG(1) << "MediaTransportRemoved: " << object_path.value(); + StateChanged(BluetoothAudioSink::STATE_DISCONNECTED); + } +} + +void BluetoothAudioSinkChromeOS::MediaTransportPropertyChanged( + const ObjectPath& object_path, + const std::string& property_name) { + if (object_path != transport_path_) + return; + + VLOG(1) << "MediaTransportPropertyChanged: " << property_name; + + // Retrieves the property set of the transport object with |object_path|. + bluez::BluetoothMediaTransportClient::Properties* properties = + bluez::BluezDBusManager::Get() + ->GetBluetoothMediaTransportClient() + ->GetProperties(object_path); + + // Dispatches a property changed event to the corresponding handler. + if (property_name == properties->state.name()) { + if (properties->state.value() == + bluez::BluetoothMediaTransportClient::kStateIdle) { + StateChanged(BluetoothAudioSink::STATE_IDLE); + } else if (properties->state.value() == + bluez::BluetoothMediaTransportClient::kStatePending) { + StateChanged(BluetoothAudioSink::STATE_PENDING); + } else if (properties->state.value() == + bluez::BluetoothMediaTransportClient::kStateActive) { + StateChanged(BluetoothAudioSink::STATE_ACTIVE); + } + } else if (property_name == properties->volume.name()) { + VolumeChanged(properties->volume.value()); + } +} + +void BluetoothAudioSinkChromeOS::SetConfiguration( + const ObjectPath& transport_path, + const TransportProperties& properties) { + VLOG(1) << "SetConfiguration"; + transport_path_ = transport_path; + + // The initial state for a connection should be "idle". + if (properties.state != bluez::BluetoothMediaTransportClient::kStateIdle) { + VLOG(1) << "SetConfiugration - unexpected state :" << properties.state; + return; + } + + // Updates |volume_| if the volume level is provided in |properties|. + if (properties.volume.get()) { + VolumeChanged(*properties.volume); + } + + StateChanged(BluetoothAudioSink::STATE_IDLE); +} + +void BluetoothAudioSinkChromeOS::SelectConfiguration( + const std::vector<uint8_t>& capabilities, + const SelectConfigurationCallback& callback) { + VLOG(1) << "SelectConfiguration"; + callback.Run(options_.capabilities); +} + +void BluetoothAudioSinkChromeOS::ClearConfiguration( + const ObjectPath& transport_path) { + if (transport_path != transport_path_) + return; + + VLOG(1) << "ClearConfiguration"; + StateChanged(BluetoothAudioSink::STATE_DISCONNECTED); +} + +void BluetoothAudioSinkChromeOS::Released() { + VLOG(1) << "Released"; + StateChanged(BluetoothAudioSink::STATE_INVALID); +} + +void BluetoothAudioSinkChromeOS::OnFileCanReadWithoutBlocking(int fd) { + ReadFromFile(); +} + +void BluetoothAudioSinkChromeOS::OnFileCanWriteWithoutBlocking(int fd) { + // Do nothing for now. +} + +void BluetoothAudioSinkChromeOS::AcquireFD() { + VLOG(1) << "AcquireFD - transport path: " << transport_path_.value(); + + read_has_failed_ = false; + + bluez::BluezDBusManager::Get()->GetBluetoothMediaTransportClient()->Acquire( + transport_path_, + base::Bind(&BluetoothAudioSinkChromeOS::OnAcquireSucceeded, + weak_ptr_factory_.GetWeakPtr()), + base::Bind(&BluetoothAudioSinkChromeOS::OnAcquireFailed, + weak_ptr_factory_.GetWeakPtr())); +} + +void BluetoothAudioSinkChromeOS::WatchFD() { + CHECK(file_.get() && file_->IsValid()); + + VLOG(1) << "WatchFD - file: " << file_->GetPlatformFile() + << ", file validity: " << file_->IsValid(); + + base::MessageLoopForIO::current()->WatchFileDescriptor( + file_->GetPlatformFile(), true, base::MessageLoopForIO::WATCH_READ, + &fd_read_watcher_, this); +} + +void BluetoothAudioSinkChromeOS::StopWatchingFD() { + if (!file_.get()) { + VLOG(1) << "StopWatchingFD - skip"; + return; + } + + bool stopped = fd_read_watcher_.StopWatchingFileDescriptor(); + VLOG(1) << "StopWatchingFD - watch stopped: " << stopped; + CHECK(stopped); + + read_mtu_ = kInvalidReadMtu; + write_mtu_ = kInvalidWriteMtu; + file_.reset(); // This will close the file descriptor. +} + +void BluetoothAudioSinkChromeOS::ReadFromFile() { + DCHECK(file_.get() && file_->IsValid()); + DCHECK(data_.get()); + + int size = file_->ReadAtCurrentPosNoBestEffort(data_.get(), read_mtu_); + + if (size == -1) { + // To reduce the number of logs, log only once for multiple failures. + if (!read_has_failed_) { + VLOG(1) << "ReadFromFile - failed"; + read_has_failed_ = true; + } + return; + } + + VLOG(1) << "ReadFromFile - read " << size << " bytes"; + FOR_EACH_OBSERVER( + BluetoothAudioSink::Observer, observers_, + BluetoothAudioSinkDataAvailable(this, data_.get(), size, read_mtu_)); +} + +void BluetoothAudioSinkChromeOS::StateChanged( + BluetoothAudioSink::State state) { + if (state == state_) + return; + + VLOG(1) << "StateChanged - state: " << StateToString(state); + + switch (state) { + case BluetoothAudioSink::STATE_INVALID: + ResetMedia(); + ResetEndpoint(); + case BluetoothAudioSink::STATE_DISCONNECTED: + ResetTransport(); + break; + case BluetoothAudioSink::STATE_IDLE: + StopWatchingFD(); + break; + case BluetoothAudioSink::STATE_PENDING: + AcquireFD(); + break; + case BluetoothAudioSink::STATE_ACTIVE: + WatchFD(); + break; + default: + break; + } + + state_ = state; + FOR_EACH_OBSERVER(BluetoothAudioSink::Observer, observers_, + BluetoothAudioSinkStateChanged(this, state_)); +} + +void BluetoothAudioSinkChromeOS::VolumeChanged(uint16_t volume) { + if (volume == volume_) + return; + + VLOG(1) << "VolumeChanged: " << volume; + + volume_ = std::min(volume, BluetoothAudioSink::kInvalidVolume); + FOR_EACH_OBSERVER(BluetoothAudioSink::Observer, observers_, + BluetoothAudioSinkVolumeChanged(this, volume_)); +} + +void BluetoothAudioSinkChromeOS::OnRegisterSucceeded( + const base::Closure& callback) { + DCHECK(media_endpoint_.get()); + VLOG(1) << "OnRegisterSucceeded"; + + StateChanged(BluetoothAudioSink::STATE_DISCONNECTED); + callback.Run(); +} + +void BluetoothAudioSinkChromeOS::OnRegisterFailed( + const BluetoothAudioSink::ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "OnRegisterFailed - error name: " << error_name + << ", error message: " << error_message; + + ResetEndpoint(); + error_callback.Run(BluetoothAudioSink::ERROR_NOT_REGISTERED); +} + +void BluetoothAudioSinkChromeOS::OnUnregisterSucceeded( + const base::Closure& callback) { + VLOG(1) << "Unregistered - endpoint: " << endpoint_path_.value(); + + // Once the state becomes STATE_INVALID, media, media transport and media + // endpoint will be reset. + StateChanged(BluetoothAudioSink::STATE_INVALID); + callback.Run(); +} + +void BluetoothAudioSinkChromeOS::OnUnregisterFailed( + const device::BluetoothAudioSink::ErrorCallback& error_callback, + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "OnUnregisterFailed - error name: " << error_name + << ", error message: " << error_message; + + error_callback.Run(BluetoothAudioSink::ERROR_NOT_UNREGISTERED); +} + +void BluetoothAudioSinkChromeOS::OnAcquireSucceeded( + dbus::FileDescriptor* fd, + const uint16_t read_mtu, + const uint16_t write_mtu) { + CHECK(fd); + fd->CheckValidity(); + CHECK(fd->is_valid() && fd->value() != kInvalidFd); + CHECK_GT(read_mtu, kInvalidReadMtu); + CHECK_GT(write_mtu, kInvalidWriteMtu); + + // Avoids unnecessary memory reallocation if read MTU doesn't change. + if (read_mtu != read_mtu_) { + read_mtu_ = read_mtu; + data_.reset(new char[read_mtu_]); + VLOG(1) << "OnAcquireSucceeded - allocate " << read_mtu_ + << " bytes of memory"; + } + + write_mtu_ = write_mtu; + + // Avoids closing the same file descriptor caused by reassignment. + if (!file_.get() || file_->GetPlatformFile() != fd->value()) { + // Takes ownership of the file descriptor. + file_.reset(new base::File(fd->TakeValue())); + DCHECK(file_->IsValid()); + VLOG(1) << "OnAcquireSucceeded - update file"; + } + + VLOG(1) << "OnAcquireSucceeded - file: " << file_->GetPlatformFile() + << ", read MTU: " << read_mtu_ << ", write MTU: " << write_mtu_; +} + +void BluetoothAudioSinkChromeOS::OnAcquireFailed( + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "OnAcquireFailed - error name: " << error_name + << ", error message: " << error_message; +} + +void BluetoothAudioSinkChromeOS::OnReleaseFDSucceeded() { + VLOG(1) << "OnReleaseFDSucceeded"; +} + +void BluetoothAudioSinkChromeOS::OnReleaseFDFailed( + const std::string& error_name, + const std::string& error_message) { + VLOG(1) << "OnReleaseFDFailed - error name: " << error_name + << ", error message: " << error_message; +} + +void BluetoothAudioSinkChromeOS::ResetMedia() { + VLOG(1) << "ResetMedia"; + + media_path_ = dbus::ObjectPath(""); +} + +void BluetoothAudioSinkChromeOS::ResetTransport() { + if (!transport_path_.IsValid()) { + VLOG(1) << "ResetTransport - skip"; + return; + } + + VLOG(1) << "ResetTransport - clean-up"; + + VolumeChanged(BluetoothAudioSink::kInvalidVolume); + transport_path_ = dbus::ObjectPath(""); + read_mtu_ = kInvalidReadMtu; + write_mtu_ = kInvalidWriteMtu; + file_.reset(); +} + +void BluetoothAudioSinkChromeOS::ResetEndpoint() { + VLOG(1) << "ResetEndpoint"; + + endpoint_path_ = ObjectPath(""); + media_endpoint_ = nullptr; +} + +} // namespace chromeos |