blob: aea3c8d0cff2c6f7f98c5333f0dd41b749ee4708 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "low_energy_central_fidl_impl.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/strings/string_printf.h"
#include "app.h"
#include "fidl_helpers.h"
// The internal library components and the generated FIDL bindings are both
// declared under the "bluetooth" namespace. We define an alias here to
// disambiguate.
namespace btfidl = ::bluetooth;
namespace bluetooth_service {
LowEnergyCentralFidlImpl::LowEnergyCentralFidlImpl(
AdapterManager* adapter_manager,
::fidl::InterfaceRequest<::btfidl::low_energy::Central> request,
const ConnectionErrorHandler& connection_error_handler)
: adapter_manager_(adapter_manager),
requesting_scan_(false),
binding_(this, std::move(request)),
weak_ptr_factory_(this) {
FXL_DCHECK(adapter_manager_);
FXL_DCHECK(connection_error_handler);
adapter_manager_->AddObserver(this);
binding_.set_connection_error_handler(
[this, connection_error_handler] { connection_error_handler(this); });
}
LowEnergyCentralFidlImpl::~LowEnergyCentralFidlImpl() {
adapter_manager_->RemoveObserver(this);
}
void LowEnergyCentralFidlImpl::SetDelegate(
::fidl::InterfaceHandle<::btfidl::low_energy::CentralDelegate> delegate) {
if (!delegate) {
FXL_VLOG(1) << "Cannot set a null delegate";
return;
}
delegate_ =
::btfidl::low_energy::CentralDelegatePtr::Create(std::move(delegate));
delegate_.set_connection_error_handler([this] {
FXL_VLOG(1) << "LowEnergyCentral delegate disconnected";
delegate_ = nullptr;
});
}
void LowEnergyCentralFidlImpl::GetPeripherals(
::fidl::Array<::fidl::String> service_uuids,
const GetPeripheralsCallback& callback) {
// TODO:
FXL_NOTIMPLEMENTED();
}
void LowEnergyCentralFidlImpl::GetPeripheral(
const ::fidl::String& identifier,
const GetPeripheralCallback& callback) {
// TODO:
FXL_NOTIMPLEMENTED();
}
void LowEnergyCentralFidlImpl::StartScan(
::btfidl::low_energy::ScanFilterPtr filter,
const StartScanCallback& callback) {
FXL_VLOG(1) << "Low Energy Central StartScan()";
if (!adapter_manager_->GetActiveAdapter()) {
FXL_VLOG(1) << "Adapter not available";
callback(fidl_helpers::NewErrorStatus(
::btfidl::ErrorCode::BLUETOOTH_NOT_AVAILABLE,
"Bluetooth not available on the current system"));
return;
}
if (requesting_scan_) {
FXL_VLOG(1) << "Scan request already in progress";
callback(fidl_helpers::NewErrorStatus(::btfidl::ErrorCode::IN_PROGRESS,
"Scan request in progress"));
return;
}
if (filter && !fidl_helpers::IsScanFilterValid(*filter)) {
FXL_VLOG(1) << "Invalid scan filter given";
callback(
fidl_helpers::NewErrorStatus(::btfidl::ErrorCode::INVALID_ARGUMENTS,
"ScanFilter contains an invalid UUID"));
return;
}
if (scan_session_) {
// A scan is already in progress. Update its filter and report success.
scan_session_->ResetToDefault();
fidl_helpers::PopulateDiscoveryFilter(*filter, scan_session_->filter());
callback(::btfidl::Status::New());
return;
}
requesting_scan_ = true;
adapter_manager_->GetActiveAdapter()->le_discovery_manager()->StartDiscovery(
fxl::MakeCopyable([
self = weak_ptr_factory_.GetWeakPtr(), filter = std::move(filter),
callback
](auto session) {
if (!self)
return;
self->requesting_scan_ = false;
if (!session) {
FXL_VLOG(1) << "Failed to start discovery session";
callback(fidl_helpers::NewErrorStatus(
::btfidl::ErrorCode::FAILED,
"Failed to start discovery session"));
return;
}
// Assign the filter contents if a filter was provided.
if (filter)
fidl_helpers::PopulateDiscoveryFilter(*filter, session->filter());
session->SetResultCallback([self](const auto& device) {
if (self)
self->OnScanResult(device);
});
self->scan_session_ = std::move(session);
self->NotifyScanStateChanged(true);
callback(::btfidl::Status::New());
}));
}
void LowEnergyCentralFidlImpl::StopScan() {
FXL_VLOG(1) << "Low Energy Central StopScan()";
if (!scan_session_) {
FXL_VLOG(1) << "No active discovery session; nothing to do";
return;
}
scan_session_ = nullptr;
NotifyScanStateChanged(false);
}
void LowEnergyCentralFidlImpl::ConnectPeripheral(
const ::fidl::String& identifier,
const ConnectPeripheralCallback& callback) {
FXL_VLOG(1) << "Low Energy Central ConnectPeripheral()";
if (!adapter_manager_->GetActiveAdapter()) {
FXL_VLOG(1) << "Adapter not available";
callback(fidl_helpers::NewErrorStatus(
::btfidl::ErrorCode::BLUETOOTH_NOT_AVAILABLE,
"Bluetooth not available on the current system"));
return;
}
auto iter = connections_.find(identifier);
if (iter != connections_.end()) {
if (iter->second) {
callback(fidl_helpers::NewErrorStatus(
::btfidl::ErrorCode::ALREADY, "Already connected to requested peer"));
} else {
callback(fidl_helpers::NewErrorStatus(::btfidl::ErrorCode::IN_PROGRESS,
"Connect request pending"));
}
return;
}
auto self = weak_ptr_factory_.GetWeakPtr();
auto conn_cb =
[ self, callback, id = identifier.get() ](auto status, auto conn_ref) {
if (!self)
return;
auto iter = self->connections_.find(id);
if (iter == self->connections_.end()) {
FXL_VLOG(1) << "Connect request canceled";
auto error = fidl_helpers::NewErrorStatus(::btfidl::ErrorCode::FAILED,
"Connect request canceled");
callback(std::move(error));
return;
}
if (status != ::bluetooth::hci::Status::kSuccess) {
FXL_DCHECK(!conn_ref);
auto msg =
fxl::StringPrintf("Failed to connect to device (id: %s)", id.c_str());
FXL_VLOG(1) << msg;
// TODO(armansito): Report PROTOCOL_ERROR only if |status| correspond to
// an actual HCI error reported from the controller. LE conn mgr currently
// uses HCI error codes for internal errors which needs to change.
auto error = fidl_helpers::NewErrorStatus(
::btfidl::ErrorCode::PROTOCOL_ERROR, msg);
error->error->protocol_error_code = status;
callback(std::move(error));
return;
}
FXL_DCHECK(conn_ref);
FXL_DCHECK(id == conn_ref->device_identifier());
if (!iter->second) {
// This is in response to a pending connect request.
conn_ref->set_closed_callback([self, id] {
if (!self)
return;
self->connections_.erase(id);
self->NotifyPeripheralDisconnected(id);
});
self->connections_[id] = std::move(conn_ref);
} else {
// This can happen if a connect is requested after a previous request was
// canceled (e.g. if ConnectPeripheral, DisconnectPeripheral,
// ConnectPeripheral are called in quick succession). In this case we
// don't claim |conn_ref| since we already have a reference for this
// peripheral.
FXL_VLOG(2) << "Dropping extra connection ref due to previously canceled "
"connection attempt";
}
callback(::btfidl::Status::New());
};
if (!adapter_manager_->GetActiveAdapter()->le_connection_manager()->Connect(
identifier.get(), conn_cb)) {
auto msg = fxl::StringPrintf("Cannot connect to unknown device id: %s",
identifier.get().c_str());
FXL_VLOG(1) << msg;
callback(fidl_helpers::NewErrorStatus(::btfidl::ErrorCode::NOT_FOUND, msg));
return;
}
connections_[identifier] = nullptr;
}
void LowEnergyCentralFidlImpl::DisconnectPeripheral(
const ::fidl::String& identifier,
const DisconnectPeripheralCallback& callback) {
auto iter = connections_.find(identifier.get());
if (iter == connections_.end()) {
auto msg = fxl::StringPrintf("Client not connected to device (id: %s)",
identifier.get().c_str());
FXL_VLOG(1) << msg;
callback(fidl_helpers::NewErrorStatus(::btfidl::ErrorCode::NOT_FOUND, msg));
return;
}
// If a request to this device is pending then the request will be canceled.
bool was_pending = !iter->second;
connections_.erase(iter);
if (was_pending) {
FXL_VLOG(1) << "Canceling ConnectPeripheral";
} else {
NotifyPeripheralDisconnected(identifier.get());
}
callback(::btfidl::Status::New());
}
void LowEnergyCentralFidlImpl::OnActiveAdapterChanged(
bluetooth::gap::Adapter* adapter) {
FXL_VLOG(1) << "The active adapter has changed; terminating all running LE "
"Central procedures";
if (scan_session_)
StopScan();
for (auto& iter : connections_) {
NotifyPeripheralDisconnected(iter.first);
}
connections_.clear();
}
void LowEnergyCentralFidlImpl::OnScanResult(
const ::bluetooth::gap::RemoteDevice& remote_device) {
if (!delegate_)
return;
auto fidl_device = fidl_helpers::NewLERemoteDevice(remote_device);
if (!fidl_device) {
FXL_VLOG(1) << "Ignoring malformed scan result";
return;
}
if (remote_device.rssi() != ::bluetooth::hci::kRSSIInvalid) {
fidl_device->rssi = ::btfidl::Int8::New();
fidl_device->rssi->value = remote_device.rssi();
}
delegate_->OnDeviceDiscovered(std::move(fidl_device));
}
void LowEnergyCentralFidlImpl::NotifyScanStateChanged(bool scanning) {
if (delegate_)
delegate_->OnScanStateChanged(scanning);
}
void LowEnergyCentralFidlImpl::NotifyPeripheralDisconnected(
const std::string& identifier) {
if (delegate_)
delegate_->OnPeripheralDisconnected(identifier);
}
} // namespace bluetooth_service