blob: bf50796f069ad705d4814d4420bdb370335b7beb [file] [log] [blame]
// 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 "components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h"
#include <string>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/thread_task_runner_handle.h"
#include "components/proximity_auth/ble/bluetooth_low_energy_connection.h"
#include "components/proximity_auth/ble/bluetooth_low_energy_device_whitelist.h"
#include "components/proximity_auth/logging/logging.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_discovery_session.h"
#include "device/bluetooth/bluetooth_uuid.h"
using device::BluetoothAdapter;
using device::BluetoothDevice;
using device::BluetoothGattConnection;
using device::BluetoothDiscoveryFilter;
namespace proximity_auth {
namespace {
const int kMinDiscoveryRSSI = -90;
} // namespace
class BluetoothThrottler;
BluetoothLowEnergyConnectionFinder::BluetoothLowEnergyConnectionFinder(
const RemoteDevice remote_device,
const std::string& remote_service_uuid,
FinderStrategy finder_strategy,
const BluetoothLowEnergyDeviceWhitelist* device_whitelist,
BluetoothThrottler* bluetooth_throttler,
int max_number_of_tries)
: remote_device_(remote_device),
remote_service_uuid_(device::BluetoothUUID(remote_service_uuid)),
finder_strategy_(finder_strategy),
device_whitelist_(device_whitelist),
bluetooth_throttler_(bluetooth_throttler),
max_number_of_tries_(max_number_of_tries),
weak_ptr_factory_(this) {
DCHECK(finder_strategy_ == FIND_ANY_DEVICE ||
!remote_device.bluetooth_address.empty());
}
BluetoothLowEnergyConnectionFinder::~BluetoothLowEnergyConnectionFinder() {
if (discovery_session_) {
StopDiscoverySession();
}
if (connection_) {
connection_->RemoveObserver(this);
connection_.reset();
}
if (adapter_) {
adapter_->RemoveObserver(this);
adapter_ = NULL;
}
}
void BluetoothLowEnergyConnectionFinder::Find(
const ConnectionCallback& connection_callback) {
if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) {
PA_LOG(WARNING) << "Bluetooth is unsupported on this platform. Aborting.";
return;
}
PA_LOG(INFO) << "Finding connection";
connection_callback_ = connection_callback;
device::BluetoothAdapterFactory::GetAdapter(
base::Bind(&BluetoothLowEnergyConnectionFinder::OnAdapterInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
// It's not necessary to observe |AdapterPresentChanged| too. When |adapter_| is
// present, but not powered, it's not possible to scan for new devices.
void BluetoothLowEnergyConnectionFinder::AdapterPoweredChanged(
BluetoothAdapter* adapter,
bool powered) {
DCHECK_EQ(adapter_.get(), adapter);
PA_LOG(INFO) << "Adapter powered: " << powered;
// Important: do not rely on |adapter->IsDiscoverying()| to verify if there is
// an active discovery session. We need to create our own with an specific
// filter.
if (powered && (!discovery_session_ || !discovery_session_->IsActive()))
StartDiscoverySession();
}
void BluetoothLowEnergyConnectionFinder::DeviceAdded(BluetoothAdapter* adapter,
BluetoothDevice* device) {
DCHECK_EQ(adapter_.get(), adapter);
DCHECK(device);
// Note: Only consider |device| when it was actually added/updated during a
// scanning, otherwise the device is stale and the GATT connection will fail.
// For instance, when |adapter_| change status from unpowered to powered,
// |DeviceAdded| is called for each paired |device|.
if (adapter_->IsPowered() && discovery_session_ &&
discovery_session_->IsActive())
HandleDeviceUpdated(device);
}
void BluetoothLowEnergyConnectionFinder::DeviceChanged(
BluetoothAdapter* adapter,
BluetoothDevice* device) {
DCHECK_EQ(adapter_.get(), adapter);
DCHECK(device);
// Note: Only consider |device| when it was actually added/updated during a
// scanning, otherwise the device is stale and the GATT connection will fail.
// For instance, when |adapter_| change status from unpowered to powered,
// |DeviceAdded| is called for each paired |device|.
if (adapter_->IsPowered() && discovery_session_ &&
discovery_session_->IsActive())
HandleDeviceUpdated(device);
}
void BluetoothLowEnergyConnectionFinder::HandleDeviceUpdated(
BluetoothDevice* device) {
// Ensuring only one call to |CreateConnection()| is made. A new |connection_|
// can be created only when the previous one disconnects, triggering a call to
// |OnConnectionStatusChanged|.
if (connection_)
return;
if (IsRightDevice(device)) {
PA_LOG(INFO) << "Connecting to device " << device->GetAddress()
<< " with service (" << HasService(device)
<< ") and is paired (" << device->IsPaired();
connection_ = CreateConnection(device->GetAddress());
connection_->AddObserver(this);
connection_->Connect();
StopDiscoverySession();
}
}
bool BluetoothLowEnergyConnectionFinder::IsRightDevice(
BluetoothDevice* device) {
if (!device)
return false;
// TODO(sacomoto): Remove it when ProximityAuthBleSystem is not needed
// anymore.
if (device_whitelist_)
return device->IsPaired() &&
(HasService(device) ||
device_whitelist_->HasDeviceWithAddress(device->GetAddress()));
// The device should be paired when looking for BLE devices by bluetooth
// address.
if (finder_strategy_ == FIND_PAIRED_DEVICE)
return device->IsPaired() &&
device->GetAddress() == remote_device_.bluetooth_address;
return HasService(device);
}
bool BluetoothLowEnergyConnectionFinder::HasService(
BluetoothDevice* remote_device) {
if (remote_device) {
PA_LOG(INFO) << "Device " << remote_device->GetAddress() << " has "
<< remote_device->GetUUIDs().size() << " services.";
std::vector<device::BluetoothUUID> uuids = remote_device->GetUUIDs();
for (const auto& service_uuid : uuids) {
if (remote_service_uuid_ == service_uuid) {
return true;
}
}
}
return false;
}
void BluetoothLowEnergyConnectionFinder::OnAdapterInitialized(
scoped_refptr<BluetoothAdapter> adapter) {
PA_LOG(INFO) << "Adapter ready";
adapter_ = adapter;
adapter_->AddObserver(this);
// This is important for debugging. To eliminate the case where the device was
// removed (forgotten) by the user, or BlueZ didn't load the device correctly.
if (finder_strategy_ == FIND_PAIRED_DEVICE) {
PA_LOG(INFO) << "Looking for paired device: "
<< remote_device_.bluetooth_address;
for (auto& device : adapter_->GetDevices()) {
if (device->IsPaired())
PA_LOG(INFO) << device->GetAddress() << " is paired";
}
}
// Note: It's possible to connect to the paired directly, so when using
// FIND_PAIRED_DEVICE strategy this is not necessary. However, the discovery
// doesn't add a lot of latency, and the makes the code path for both
// strategies more similar.
StartDiscoverySession();
}
void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted(
scoped_ptr<device::BluetoothDiscoverySession> discovery_session) {
PA_LOG(INFO) << "Discovery session started";
discovery_session_ = discovery_session.Pass();
}
void BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError() {
PA_LOG(WARNING) << "Error starting discovery session";
}
void BluetoothLowEnergyConnectionFinder::StartDiscoverySession() {
DCHECK(adapter_);
if (discovery_session_ && discovery_session_->IsActive()) {
PA_LOG(INFO) << "Discovery session already active";
return;
}
// Discover only low energy (LE) devices with strong enough signal.
scoped_ptr<BluetoothDiscoveryFilter> filter(new BluetoothDiscoveryFilter(
BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
filter->SetRSSI(kMinDiscoveryRSSI);
adapter_->StartDiscoverySessionWithFilter(
filter.Pass(),
base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted,
weak_ptr_factory_.GetWeakPtr()),
base::Bind(
&BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothLowEnergyConnectionFinder::StopDiscoverySession() {
PA_LOG(INFO) << "Stopping discovery session";
// Destroying the discovery session also stops it.
discovery_session_.reset();
}
scoped_ptr<Connection> BluetoothLowEnergyConnectionFinder::CreateConnection(
const std::string& device_address) {
DCHECK(remote_device_.bluetooth_address.empty() ||
remote_device_.bluetooth_address == device_address);
remote_device_.bluetooth_address = device_address;
return make_scoped_ptr(new BluetoothLowEnergyConnection(
remote_device_, adapter_, remote_service_uuid_, bluetooth_throttler_,
max_number_of_tries_));
}
void BluetoothLowEnergyConnectionFinder::OnConnectionStatusChanged(
Connection* connection,
Connection::Status old_status,
Connection::Status new_status) {
DCHECK_EQ(connection, connection_.get());
PA_LOG(INFO) << "OnConnectionStatusChanged: " << old_status << " -> "
<< new_status;
if (!connection_callback_.is_null() && connection_->IsConnected()) {
adapter_->RemoveObserver(this);
connection_->RemoveObserver(this);
// If we invoke the callback now, the callback function may install its own
// observer to |connection_|. Because we are in the ConnectionObserver
// callstack, this new observer will receive this connection event.
// Therefore, we need to invoke the callback or restart discovery
// asynchronously.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(&BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync,
weak_ptr_factory_.GetWeakPtr()));
} else if (old_status == Connection::IN_PROGRESS) {
PA_LOG(WARNING) << "Connection failed. Retrying.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(
&BluetoothLowEnergyConnectionFinder::RestartDiscoverySessionAsync,
weak_ptr_factory_.GetWeakPtr()));
}
}
void BluetoothLowEnergyConnectionFinder::RestartDiscoverySessionAsync() {
PA_LOG(INFO) << "Restarting discovery session.";
connection_.reset();
if (!discovery_session_ || !discovery_session_->IsActive())
StartDiscoverySession();
}
BluetoothDevice* BluetoothLowEnergyConnectionFinder::GetDevice(
const std::string& device_address) {
// It's not possible to simply use
// |adapter_->GetDevice(GetRemoteDeviceAddress())| to find the device with MAC
// address |GetRemoteDeviceAddress()|. For paired devices,
// BluetoothAdapter::GetDevice(XXX) searches for the temporary MAC address
// XXX, whereas |remote_device_.bluetooth_address| is the real MAC address.
// This is a bug in the way device::BluetoothAdapter is storing the devices
// (see crbug.com/497841).
std::vector<BluetoothDevice*> devices = adapter_->GetDevices();
for (const auto& device : devices) {
if (device->GetAddress() == device_address)
return device;
}
return nullptr;
}
void BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync() {
connection_callback_.Run(connection_.Pass());
}
} // namespace proximity_auth