Roll src/third_party/WebKit eac3800:0237a66 (svn 202606:202607)
[chromium-blink-merge.git] / components / proximity_auth / ble / bluetooth_low_energy_connection_finder.cc
blobacc530ae437f1d05a2932c799693dab28048b0d7
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "components/proximity_auth/ble/bluetooth_low_energy_connection.h"
17 #include "components/proximity_auth/ble/bluetooth_low_energy_device_whitelist.h"
18 #include "components/proximity_auth/logging/logging.h"
19 #include "device/bluetooth/bluetooth_adapter_factory.h"
20 #include "device/bluetooth/bluetooth_device.h"
21 #include "device/bluetooth/bluetooth_discovery_session.h"
22 #include "device/bluetooth/bluetooth_uuid.h"
24 using device::BluetoothAdapter;
25 using device::BluetoothDevice;
26 using device::BluetoothGattConnection;
27 using device::BluetoothDiscoveryFilter;
29 namespace proximity_auth {
30 namespace {
31 const int kMinDiscoveryRSSI = -90;
32 } // namespace
34 class BluetoothThrottler;
36 BluetoothLowEnergyConnectionFinder::BluetoothLowEnergyConnectionFinder(
37 const RemoteDevice remote_device,
38 const std::string& remote_service_uuid,
39 FinderStrategy finder_strategy,
40 const BluetoothLowEnergyDeviceWhitelist* device_whitelist,
41 BluetoothThrottler* bluetooth_throttler,
42 int max_number_of_tries)
43 : remote_device_(remote_device),
44 remote_service_uuid_(device::BluetoothUUID(remote_service_uuid)),
45 finder_strategy_(finder_strategy),
46 device_whitelist_(device_whitelist),
47 bluetooth_throttler_(bluetooth_throttler),
48 max_number_of_tries_(max_number_of_tries),
49 weak_ptr_factory_(this) {
50 DCHECK(finder_strategy_ == FIND_ANY_DEVICE ||
51 !remote_device.bluetooth_address.empty());
54 BluetoothLowEnergyConnectionFinder::~BluetoothLowEnergyConnectionFinder() {
55 if (discovery_session_) {
56 StopDiscoverySession();
59 if (connection_) {
60 connection_->RemoveObserver(this);
61 connection_.reset();
64 if (adapter_) {
65 adapter_->RemoveObserver(this);
66 adapter_ = NULL;
70 void BluetoothLowEnergyConnectionFinder::Find(
71 const ConnectionCallback& connection_callback) {
72 if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) {
73 PA_LOG(WARNING) << "Bluetooth is unsupported on this platform. Aborting.";
74 return;
76 PA_LOG(INFO) << "Finding connection";
78 connection_callback_ = connection_callback;
80 device::BluetoothAdapterFactory::GetAdapter(
81 base::Bind(&BluetoothLowEnergyConnectionFinder::OnAdapterInitialized,
82 weak_ptr_factory_.GetWeakPtr()));
85 // It's not necessary to observe |AdapterPresentChanged| too. When |adapter_| is
86 // present, but not powered, it's not possible to scan for new devices.
87 void BluetoothLowEnergyConnectionFinder::AdapterPoweredChanged(
88 BluetoothAdapter* adapter,
89 bool powered) {
90 DCHECK_EQ(adapter_.get(), adapter);
91 PA_LOG(INFO) << "Adapter powered: " << powered;
93 // Important: do not rely on |adapter->IsDiscoverying()| to verify if there is
94 // an active discovery session. We need to create our own with an specific
95 // filter.
96 if (powered && (!discovery_session_ || !discovery_session_->IsActive()))
97 StartDiscoverySession();
100 void BluetoothLowEnergyConnectionFinder::DeviceAdded(BluetoothAdapter* adapter,
101 BluetoothDevice* device) {
102 DCHECK_EQ(adapter_.get(), adapter);
103 DCHECK(device);
105 // Note: Only consider |device| when it was actually added/updated during a
106 // scanning, otherwise the device is stale and the GATT connection will fail.
107 // For instance, when |adapter_| change status from unpowered to powered,
108 // |DeviceAdded| is called for each paired |device|.
109 if (adapter_->IsPowered() && discovery_session_ &&
110 discovery_session_->IsActive())
111 HandleDeviceUpdated(device);
114 void BluetoothLowEnergyConnectionFinder::DeviceChanged(
115 BluetoothAdapter* adapter,
116 BluetoothDevice* device) {
117 DCHECK_EQ(adapter_.get(), adapter);
118 DCHECK(device);
120 // Note: Only consider |device| when it was actually added/updated during a
121 // scanning, otherwise the device is stale and the GATT connection will fail.
122 // For instance, when |adapter_| change status from unpowered to powered,
123 // |DeviceAdded| is called for each paired |device|.
124 if (adapter_->IsPowered() && discovery_session_ &&
125 discovery_session_->IsActive())
126 HandleDeviceUpdated(device);
129 void BluetoothLowEnergyConnectionFinder::HandleDeviceUpdated(
130 BluetoothDevice* device) {
131 // Ensuring only one call to |CreateConnection()| is made. A new |connection_|
132 // can be created only when the previous one disconnects, triggering a call to
133 // |OnConnectionStatusChanged|.
134 if (connection_)
135 return;
137 if (IsRightDevice(device)) {
138 PA_LOG(INFO) << "Connecting to device " << device->GetAddress()
139 << " with service (" << HasService(device)
140 << ") and is paired (" << device->IsPaired();
142 connection_ = CreateConnection(device->GetAddress());
143 connection_->AddObserver(this);
144 connection_->Connect();
146 StopDiscoverySession();
150 bool BluetoothLowEnergyConnectionFinder::IsRightDevice(
151 BluetoothDevice* device) {
152 if (!device)
153 return false;
155 // TODO(sacomoto): Remove it when ProximityAuthBleSystem is not needed
156 // anymore.
157 if (device_whitelist_)
158 return device->IsPaired() &&
159 (HasService(device) ||
160 device_whitelist_->HasDeviceWithAddress(device->GetAddress()));
162 // The device should be paired when looking for BLE devices by bluetooth
163 // address.
164 if (finder_strategy_ == FIND_PAIRED_DEVICE)
165 return device->IsPaired() &&
166 device->GetAddress() == remote_device_.bluetooth_address;
167 return HasService(device);
170 bool BluetoothLowEnergyConnectionFinder::HasService(
171 BluetoothDevice* remote_device) {
172 if (remote_device) {
173 PA_LOG(INFO) << "Device " << remote_device->GetAddress() << " has "
174 << remote_device->GetUUIDs().size() << " services.";
175 std::vector<device::BluetoothUUID> uuids = remote_device->GetUUIDs();
176 for (const auto& service_uuid : uuids) {
177 if (remote_service_uuid_ == service_uuid) {
178 return true;
182 return false;
185 void BluetoothLowEnergyConnectionFinder::OnAdapterInitialized(
186 scoped_refptr<BluetoothAdapter> adapter) {
187 PA_LOG(INFO) << "Adapter ready";
189 adapter_ = adapter;
190 adapter_->AddObserver(this);
192 // Note: it's not possible to connect with the paired directly, as the
193 // temporary MAC may not be resolved automatically (see crbug.com/495402). The
194 // Bluetooth adapter will fire |OnDeviceChanged| notifications for all
195 // Bluetooth Low Energy devices that are advertising.
196 StartDiscoverySession();
199 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted(
200 scoped_ptr<device::BluetoothDiscoverySession> discovery_session) {
201 PA_LOG(INFO) << "Discovery session started";
202 discovery_session_ = discovery_session.Pass();
205 void BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError() {
206 PA_LOG(WARNING) << "Error starting discovery session";
209 void BluetoothLowEnergyConnectionFinder::StartDiscoverySession() {
210 DCHECK(adapter_);
211 if (discovery_session_ && discovery_session_->IsActive()) {
212 PA_LOG(INFO) << "Discovery session already active";
213 return;
216 // Discover only low energy (LE) devices with strong enough signal.
217 scoped_ptr<BluetoothDiscoveryFilter> filter(new BluetoothDiscoveryFilter(
218 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
219 filter->SetRSSI(kMinDiscoveryRSSI);
221 adapter_->StartDiscoverySessionWithFilter(
222 filter.Pass(),
223 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted,
224 weak_ptr_factory_.GetWeakPtr()),
225 base::Bind(
226 &BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError,
227 weak_ptr_factory_.GetWeakPtr()));
230 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped() {
231 PA_LOG(INFO) << "Discovery session stopped";
232 discovery_session_.reset();
235 void BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError() {
236 PA_LOG(WARNING) << "Error stopping discovery session";
239 void BluetoothLowEnergyConnectionFinder::StopDiscoverySession() {
240 PA_LOG(INFO) << "Stopping discovery session";
242 if (!adapter_) {
243 PA_LOG(WARNING) << "Adapter not initialized";
244 return;
246 if (!discovery_session_ || !discovery_session_->IsActive()) {
247 PA_LOG(INFO) << "No Active discovery session";
248 return;
251 discovery_session_->Stop(
252 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped,
253 weak_ptr_factory_.GetWeakPtr()),
254 base::Bind(
255 &BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError,
256 weak_ptr_factory_.GetWeakPtr()));
259 scoped_ptr<Connection> BluetoothLowEnergyConnectionFinder::CreateConnection(
260 const std::string& device_address) {
261 DCHECK(remote_device_.bluetooth_address.empty() ||
262 remote_device_.bluetooth_address == device_address);
263 remote_device_.bluetooth_address = device_address;
264 return make_scoped_ptr(new BluetoothLowEnergyConnection(
265 remote_device_, adapter_, remote_service_uuid_, bluetooth_throttler_,
266 max_number_of_tries_));
269 void BluetoothLowEnergyConnectionFinder::OnConnectionStatusChanged(
270 Connection* connection,
271 Connection::Status old_status,
272 Connection::Status new_status) {
273 DCHECK_EQ(connection, connection_.get());
274 PA_LOG(INFO) << "OnConnectionStatusChanged: " << old_status << " -> "
275 << new_status;
277 if (!connection_callback_.is_null() && connection_->IsConnected()) {
278 adapter_->RemoveObserver(this);
279 connection_->RemoveObserver(this);
281 // If we invoke the callback now, the callback function may install its own
282 // observer to |connection_|. Because we are in the ConnectionObserver
283 // callstack, this new observer will receive this connection event.
284 // Therefore, we need to invoke the callback asynchronously.
285 base::ThreadTaskRunnerHandle::Get()->PostTask(
286 FROM_HERE,
287 base::Bind(&BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync,
288 weak_ptr_factory_.GetWeakPtr()));
289 } else if (old_status == Connection::IN_PROGRESS) {
290 PA_LOG(WARNING) << "Connection failed. Retrying.";
291 RestartDiscoverySessionWhenReady();
295 void BluetoothLowEnergyConnectionFinder::RestartDiscoverySessionWhenReady() {
296 PA_LOG(INFO) << "Trying to restart discovery.";
298 // To restart scanning for devices, it's necessary to ensure that:
299 // (i) the GATT connection to |remove_device_| is closed;
300 // (ii) there is no pending call to
301 // |device::BluetoothDiscoverySession::Stop()|.
302 // The second condition is satisfied when |OnDiscoveryStopped| is called and
303 // |discovery_session_| is reset.
304 if (!discovery_session_) {
305 PA_LOG(INFO) << "Ready to start discovery.";
306 connection_.reset();
307 StartDiscoverySession();
308 } else {
309 base::ThreadTaskRunnerHandle::Get()->PostTask(
310 FROM_HERE, base::Bind(&BluetoothLowEnergyConnectionFinder::
311 RestartDiscoverySessionWhenReady,
312 weak_ptr_factory_.GetWeakPtr()));
316 BluetoothDevice* BluetoothLowEnergyConnectionFinder::GetDevice(
317 std::string device_address) {
318 // It's not possible to simply use
319 // |adapter_->GetDevice(GetRemoteDeviceAddress())| to find the device with MAC
320 // address |GetRemoteDeviceAddress()|. For paired devices,
321 // BluetoothAdapter::GetDevice(XXX) searches for the temporary MAC address
322 // XXX, whereas |remote_device_.bluetooth_address| is the real MAC address.
323 // This is a bug in the way device::BluetoothAdapter is storing the devices
324 // (see crbug.com/497841).
325 std::vector<BluetoothDevice*> devices = adapter_->GetDevices();
326 for (const auto& device : devices) {
327 if (device->GetAddress() == device_address)
328 return device;
330 return nullptr;
333 void BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync() {
334 connection_callback_.Run(connection_.Pass());
337 } // namespace proximity_auth