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"
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/thread_task_runner_handle.h"
15 #include "components/proximity_auth/ble/bluetooth_low_energy_connection.h"
16 #include "components/proximity_auth/ble/bluetooth_low_energy_device_whitelist.h"
17 #include "components/proximity_auth/connection.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
{
31 const int kMinDiscoveryRSSI
= -90;
32 const int kDelayAfterGattConnectionMilliseconds
= 1000;
35 BluetoothLowEnergyConnectionFinder::BluetoothLowEnergyConnectionFinder(
36 const std::string
& remote_service_uuid
,
37 const std::string
& to_peripheral_char_uuid
,
38 const std::string
& from_peripheral_char_uuid
,
39 const BluetoothLowEnergyDeviceWhitelist
* device_whitelist
,
40 int max_number_of_tries
)
41 : remote_service_uuid_(device::BluetoothUUID(remote_service_uuid
)),
42 to_peripheral_char_uuid_(device::BluetoothUUID(to_peripheral_char_uuid
)),
43 from_peripheral_char_uuid_(
44 device::BluetoothUUID(from_peripheral_char_uuid
)),
45 device_whitelist_(device_whitelist
),
47 max_number_of_tries_(max_number_of_tries
),
48 delay_after_gatt_connection_(base::TimeDelta::FromMilliseconds(
49 kDelayAfterGattConnectionMilliseconds
)),
50 weak_ptr_factory_(this) {
53 BluetoothLowEnergyConnectionFinder::~BluetoothLowEnergyConnectionFinder() {
54 if (discovery_session_
) {
55 StopDiscoverySession();
59 connection_
->RemoveObserver(this);
64 adapter_
->RemoveObserver(this);
69 void BluetoothLowEnergyConnectionFinder::Find(
70 const ConnectionCallback
& connection_callback
) {
71 if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) {
72 PA_LOG(WARNING
) << "Bluetooth is unsupported on this platform. Aborting.";
75 PA_LOG(INFO
) << "Finding connection";
77 connection_callback_
= connection_callback
;
79 device::BluetoothAdapterFactory::GetAdapter(
80 base::Bind(&BluetoothLowEnergyConnectionFinder::OnAdapterInitialized
,
81 weak_ptr_factory_
.GetWeakPtr()));
84 // It's not necessary to observe |AdapterPresentChanged| too. When |adapter_| is
85 // present, but not powered, it's not possible to scan for new devices.
86 void BluetoothLowEnergyConnectionFinder::AdapterPoweredChanged(
87 BluetoothAdapter
* adapter
,
89 DCHECK_EQ(adapter_
.get(), adapter
);
90 PA_LOG(INFO
) << "Adapter powered: " << powered
;
92 // Important: do not rely on |adapter->IsDiscoverying()| to verify if there is
93 // an active discovery session. We need to create our own with an specific
95 if (powered
&& (!discovery_session_
|| !discovery_session_
->IsActive()))
96 StartDiscoverySession();
99 void BluetoothLowEnergyConnectionFinder::DeviceAdded(BluetoothAdapter
* adapter
,
100 BluetoothDevice
* device
) {
101 DCHECK_EQ(adapter_
.get(), adapter
);
103 PA_LOG(INFO
) << "Device added: " << device
->GetAddress();
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
);
119 PA_LOG(INFO
) << "Device changed: " << device
->GetAddress();
121 // Note: Only consider |device| when it was actually added/updated during a
122 // scanning, otherwise the device is stale and the GATT connection will fail.
123 // For instance, when |adapter_| change status from unpowered to powered,
124 // |DeviceAdded| is called for each paired |device|.
125 if (adapter_
->IsPowered() && discovery_session_
&&
126 discovery_session_
->IsActive())
127 HandleDeviceUpdated(device
);
130 void BluetoothLowEnergyConnectionFinder::HandleDeviceUpdated(
131 BluetoothDevice
* device
) {
134 const auto& i
= pending_connections_
.find(device
);
135 if (i
!= pending_connections_
.end()) {
136 PA_LOG(INFO
) << "Pending connection to device " << device
->GetAddress();
139 if (device
->IsPaired() &&
140 (HasService(device
) ||
141 device_whitelist_
->HasDeviceWithAddress(device
->GetAddress()))) {
142 PA_LOG(INFO
) << "Connecting to paired device " << device
->GetAddress()
143 << " with service (" << HasService(device
)
144 << ") or is whitelisted ("
145 << device_whitelist_
->HasDeviceWithAddress(
146 device
->GetAddress()) << ")";
147 pending_connections_
.insert(device
);
148 CreateGattConnection(device
);
152 void BluetoothLowEnergyConnectionFinder::DeviceRemoved(
153 BluetoothAdapter
* adapter
,
154 BluetoothDevice
* device
) {
158 const auto& i
= pending_connections_
.find(device
);
159 if (i
!= pending_connections_
.end()) {
160 PA_LOG(INFO
) << "Remove pending connection to " << device
->GetAddress();
161 pending_connections_
.erase(i
);
165 void BluetoothLowEnergyConnectionFinder::OnAdapterInitialized(
166 scoped_refptr
<BluetoothAdapter
> adapter
) {
167 PA_LOG(INFO
) << "Adapter ready";
170 adapter_
->AddObserver(this);
172 // Note: it's not possible to connect with the paired directly, as the
173 // temporary MAC may not be resolved automatically (see crbug.com/495402). The
174 // Bluetooth adapter will fire |OnDeviceChanged| notifications for all
175 // Bluetooth Low Energy devices that are advertising.
176 std::vector
<BluetoothDevice
*> devices
= adapter_
->GetDevices();
177 for (auto* device
: devices
) {
178 PA_LOG(INFO
) << "Ignoring device " << device
->GetAddress()
179 << " present when adapter was initialized.";
182 StartDiscoverySession();
185 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted(
186 scoped_ptr
<device::BluetoothDiscoverySession
> discovery_session
) {
187 PA_LOG(INFO
) << "Discovery session started";
188 discovery_session_
= discovery_session
.Pass();
191 void BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError() {
192 PA_LOG(WARNING
) << "Error starting discovery session";
195 void BluetoothLowEnergyConnectionFinder::StartDiscoverySession() {
197 if (discovery_session_
&& discovery_session_
->IsActive()) {
198 PA_LOG(INFO
) << "Discovery session already active";
202 // Discover only low energy (LE) devices with strong enough signal.
203 scoped_ptr
<BluetoothDiscoveryFilter
> filter(new BluetoothDiscoveryFilter(
204 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE
));
205 filter
->SetRSSI(kMinDiscoveryRSSI
);
207 adapter_
->StartDiscoverySessionWithFilter(
209 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted
,
210 weak_ptr_factory_
.GetWeakPtr()),
212 &BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError
,
213 weak_ptr_factory_
.GetWeakPtr()));
216 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped() {
217 PA_LOG(INFO
) << "Discovery session stopped";
218 discovery_session_
.reset();
221 void BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError() {
222 PA_LOG(WARNING
) << "Error stopping discovery session";
225 void BluetoothLowEnergyConnectionFinder::StopDiscoverySession() {
226 PA_LOG(INFO
) << "Stopping discovery sesison";
229 PA_LOG(WARNING
) << "Adapter not initialized";
232 if (!discovery_session_
|| !discovery_session_
->IsActive()) {
233 PA_LOG(INFO
) << "No Active discovery session";
237 discovery_session_
->Stop(
238 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped
,
239 weak_ptr_factory_
.GetWeakPtr()),
241 &BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError
,
242 weak_ptr_factory_
.GetWeakPtr()));
245 bool BluetoothLowEnergyConnectionFinder::HasService(
246 BluetoothDevice
* remote_device
) {
248 PA_LOG(INFO
) << "Device " << remote_device
->GetAddress() << " has "
249 << remote_device
->GetUUIDs().size() << " services.";
250 std::vector
<device::BluetoothUUID
> uuids
= remote_device
->GetUUIDs();
251 for (const auto& service_uuid
: uuids
) {
252 if (remote_service_uuid_
== service_uuid
) {
260 void BluetoothLowEnergyConnectionFinder::OnCreateGattConnectionError(
261 std::string device_address
,
262 BluetoothDevice::ConnectErrorCode error_code
) {
263 PA_LOG(WARNING
) << "Error creating connection to device " << device_address
264 << " : error code = " << error_code
;
266 BluetoothDevice
* device
= GetDevice(device_address
);
267 const auto& i
= pending_connections_
.find(device
);
268 if (i
!= pending_connections_
.end()) {
269 PA_LOG(INFO
) << "Remove pending connection to " << device
->GetAddress();
270 pending_connections_
.erase(i
);
274 void BluetoothLowEnergyConnectionFinder::OnGattConnectionCreated(
275 scoped_ptr
<BluetoothGattConnection
> gatt_connection
) {
277 CloseGattConnection(gatt_connection
.Pass());
281 PA_LOG(INFO
) << "GATT connection with " << gatt_connection
->GetDeviceAddress()
284 pending_connections_
.clear();
286 gatt_connection_
= gatt_connection
.Pass();
288 // This is a workaround for crbug.com/498850. Currently, trying to write/read
289 // characteristics immediatelly after the GATT connection was established
290 // fails with the very informative GATT_ERROR_FAILED.
291 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
293 base::Bind(&BluetoothLowEnergyConnectionFinder::CompleteConnection
,
294 weak_ptr_factory_
.GetWeakPtr()),
295 delay_after_gatt_connection_
);
297 StopDiscoverySession();
300 void BluetoothLowEnergyConnectionFinder::CompleteConnection() {
301 connection_
= CreateConnection(gatt_connection_
.Pass());
302 connection_
->AddObserver(this);
303 connection_
->Connect();
306 void BluetoothLowEnergyConnectionFinder::CreateGattConnection(
307 device::BluetoothDevice
* remote_device
) {
308 PA_LOG(INFO
) << "Creating GATT connection with "
309 << remote_device
->GetAddress();
310 remote_device
->CreateGattConnection(
311 base::Bind(&BluetoothLowEnergyConnectionFinder::OnGattConnectionCreated
,
312 weak_ptr_factory_
.GetWeakPtr()),
314 &BluetoothLowEnergyConnectionFinder::OnCreateGattConnectionError
,
315 weak_ptr_factory_
.GetWeakPtr(), remote_device
->GetAddress()));
318 void BluetoothLowEnergyConnectionFinder::CloseGattConnection(
319 scoped_ptr
<device::BluetoothGattConnection
> gatt_connection
) {
320 DCHECK(gatt_connection
);
321 PA_LOG(INFO
) << "Closing GATT connection with "
322 << gatt_connection
->GetDeviceAddress();
324 // Destroying the BluetoothGattConnection also disconnects it.
325 gatt_connection
.reset();
328 scoped_ptr
<Connection
> BluetoothLowEnergyConnectionFinder::CreateConnection(
329 scoped_ptr
<BluetoothGattConnection
> gatt_connection
) {
330 remote_device_
.bluetooth_address
= gatt_connection
->GetDeviceAddress();
332 return make_scoped_ptr(new BluetoothLowEnergyConnection(
333 remote_device_
, adapter_
, remote_service_uuid_
, to_peripheral_char_uuid_
,
334 from_peripheral_char_uuid_
, gatt_connection
.Pass(),
335 max_number_of_tries_
));
338 void BluetoothLowEnergyConnectionFinder::OnConnectionStatusChanged(
339 Connection
* connection
,
340 Connection::Status old_status
,
341 Connection::Status new_status
) {
342 DCHECK_EQ(connection
, connection_
.get());
343 PA_LOG(INFO
) << "OnConnectionStatusChanged: " << old_status
<< " -> "
346 if (!connection_callback_
.is_null() && connection_
->IsConnected()) {
347 adapter_
->RemoveObserver(this);
348 connection_
->RemoveObserver(this);
350 // Note: any observer of |connection_| added in |connection_callback_| will
351 // also receive this |OnConnectionStatusChanged| notification (IN_PROGRESS
353 connection_callback_
.Run(connection_
.Pass());
354 connection_callback_
.Reset();
355 } else if (old_status
== Connection::IN_PROGRESS
) {
356 PA_LOG(WARNING
) << "Connection failed. Retrying.";
357 RestartDiscoverySessionWhenReady();
361 void BluetoothLowEnergyConnectionFinder::RestartDiscoverySessionWhenReady() {
362 PA_LOG(INFO
) << "Trying to restart discovery.";
364 // To restart scanning for devices, it's necessary to ensure that:
365 // (i) the GATT connection to |remove_device_| is closed;
366 // (ii) there is no pending call to
367 // |device::BluetoothDiscoverySession::Stop()|.
368 // The second condition is satisfied when |OnDiscoveryStopped| is called and
369 // |discovery_session_| is reset.
370 if ((!gatt_connection_
|| !gatt_connection_
->IsConnected()) &&
371 !discovery_session_
) {
372 PA_LOG(INFO
) << "Ready to start discovery.";
375 StartDiscoverySession();
377 base::ThreadTaskRunnerHandle::Get()->PostTask(
378 FROM_HERE
, base::Bind(&BluetoothLowEnergyConnectionFinder::
379 RestartDiscoverySessionWhenReady
,
380 weak_ptr_factory_
.GetWeakPtr()));
384 void BluetoothLowEnergyConnectionFinder::SetDelayForTesting(
385 base::TimeDelta delay
) {
386 delay_after_gatt_connection_
= delay
;
389 BluetoothDevice
* BluetoothLowEnergyConnectionFinder::GetDevice(
390 std::string device_address
) {
391 // It's not possible to simply use
392 // |adapter_->GetDevice(GetRemoteDeviceAddress())| to find the device with MAC
393 // address |GetRemoteDeviceAddress()|. For paired devices,
394 // BluetoothAdapter::GetDevice(XXX) searches for the temporary MAC address
395 // XXX, whereas |remote_device_.bluetooth_address| is the real MAC address.
396 // This is a bug in the way device::BluetoothAdapter is storing the devices
397 // (see crbug.com/497841).
398 std::vector
<BluetoothDevice
*> devices
= adapter_
->GetDevices();
399 for (const auto& device
: devices
) {
400 if (device
->GetAddress() == device_address
)
406 } // namespace proximity_auth