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/connection.h"
17 #include "components/proximity_auth/logging/logging.h"
18 #include "device/bluetooth/bluetooth_adapter_factory.h"
19 #include "device/bluetooth/bluetooth_device.h"
20 #include "device/bluetooth/bluetooth_discovery_session.h"
21 #include "device/bluetooth/bluetooth_uuid.h"
23 using device::BluetoothAdapter
;
24 using device::BluetoothDevice
;
25 using device::BluetoothGattConnection
;
26 using device::BluetoothDiscoveryFilter
;
28 namespace proximity_auth
{
30 const int kMinDiscoveryRSSI
= -100;
31 const int kDelayAfterGattConnectionMilliseconds
= 1000;
34 BluetoothLowEnergyConnectionFinder::BluetoothLowEnergyConnectionFinder(
35 const std::string
& remote_service_uuid
,
36 const std::string
& to_peripheral_char_uuid
,
37 const std::string
& from_peripheral_char_uuid
,
38 int max_number_of_tries
)
39 : remote_service_uuid_(device::BluetoothUUID(remote_service_uuid
)),
40 to_peripheral_char_uuid_(device::BluetoothUUID(to_peripheral_char_uuid
)),
41 from_peripheral_char_uuid_(
42 device::BluetoothUUID(from_peripheral_char_uuid
)),
44 max_number_of_tries_(max_number_of_tries
),
45 delay_after_gatt_connection_(base::TimeDelta::FromMilliseconds(
46 kDelayAfterGattConnectionMilliseconds
)),
47 weak_ptr_factory_(this) {
50 BluetoothLowEnergyConnectionFinder::~BluetoothLowEnergyConnectionFinder() {
51 if (discovery_session_
) {
52 StopDiscoverySession();
56 connection_
->RemoveObserver(this);
61 adapter_
->RemoveObserver(this);
66 void BluetoothLowEnergyConnectionFinder::Find(
67 const ConnectionCallback
& connection_callback
) {
68 if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) {
69 PA_LOG(WARNING
) << "Bluetooth is unsupported on this platform. Aborting.";
72 PA_LOG(INFO
) << "Finding connection";
74 connection_callback_
= connection_callback
;
76 device::BluetoothAdapterFactory::GetAdapter(
77 base::Bind(&BluetoothLowEnergyConnectionFinder::OnAdapterInitialized
,
78 weak_ptr_factory_
.GetWeakPtr()));
81 // It's not necessary to observe |AdapterPresentChanged| too. When |adapter_| is
82 // present, but not powered, it's not possible to scan for new devices.
83 void BluetoothLowEnergyConnectionFinder::AdapterPoweredChanged(
84 BluetoothAdapter
* adapter
,
86 DCHECK_EQ(adapter_
.get(), adapter
);
87 PA_LOG(INFO
) << "Adapter powered: " << powered
;
89 // Important: do not rely on |adapter->IsDiscoverying()| to verify if there is
90 // an active discovery session. We need to create our own with an specific
92 if (powered
&& (!discovery_session_
|| !discovery_session_
->IsActive()))
93 StartDiscoverySession();
96 void BluetoothLowEnergyConnectionFinder::DeviceAdded(BluetoothAdapter
* adapter
,
97 BluetoothDevice
* device
) {
98 DCHECK_EQ(adapter_
.get(), adapter
);
100 PA_LOG(INFO
) << "Device added: " << device
->GetAddress();
102 // Note: Only consider |device| when it was actually added/updated during a
103 // scanning, otherwise the device is stale and the GATT connection will fail.
104 // For instance, when |adapter_| change status from unpowered to powered,
105 // |DeviceAdded| is called for each paired |device|.
106 if (adapter_
->IsPowered() && discovery_session_
&&
107 discovery_session_
->IsActive())
108 HandleDeviceUpdated(device
);
111 void BluetoothLowEnergyConnectionFinder::DeviceChanged(
112 BluetoothAdapter
* adapter
,
113 BluetoothDevice
* device
) {
114 DCHECK_EQ(adapter_
.get(), adapter
);
116 PA_LOG(INFO
) << "Device changed: " << device
->GetAddress();
118 // Note: Only consider |device| when it was actually added/updated during a
119 // scanning, otherwise the device is stale and the GATT connection will fail.
120 // For instance, when |adapter_| change status from unpowered to powered,
121 // |DeviceAdded| is called for each paired |device|.
122 if (adapter_
->IsPowered() && discovery_session_
&&
123 discovery_session_
->IsActive())
124 HandleDeviceUpdated(device
);
127 void BluetoothLowEnergyConnectionFinder::HandleDeviceUpdated(
128 BluetoothDevice
* device
) {
131 const auto& i
= pending_connections_
.find(device
);
132 if (i
!= pending_connections_
.end()) {
133 PA_LOG(INFO
) << "Pending connection to device " << device
->GetAddress();
136 if (HasService(device
) && device
->IsPaired()) {
137 PA_LOG(INFO
) << "Connecting to paired device " << device
->GetAddress();
138 pending_connections_
.insert(device
);
139 CreateGattConnection(device
);
143 void BluetoothLowEnergyConnectionFinder::DeviceRemoved(
144 BluetoothAdapter
* adapter
,
145 BluetoothDevice
* device
) {
149 const auto& i
= pending_connections_
.find(device
);
150 if (i
!= pending_connections_
.end()) {
151 PA_LOG(INFO
) << "Remove pending connection to " << device
->GetAddress();
152 pending_connections_
.erase(i
);
156 void BluetoothLowEnergyConnectionFinder::OnAdapterInitialized(
157 scoped_refptr
<BluetoothAdapter
> adapter
) {
158 PA_LOG(INFO
) << "Adapter ready";
161 adapter_
->AddObserver(this);
163 // Note: it's not possible to connect with the paired directly, as the
164 // temporary MAC may not be resolved automatically (see crbug.com/495402). The
165 // Bluetooth adapter will fire |OnDeviceChanged| notifications for all
166 // Bluetooth Low Energy devices that are advertising.
167 std::vector
<BluetoothDevice
*> devices
= adapter_
->GetDevices();
168 for (auto* device
: devices
) {
169 PA_LOG(INFO
) << "Ignoring device " << device
->GetAddress()
170 << " present when adapter was initialized.";
173 StartDiscoverySession();
176 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted(
177 scoped_ptr
<device::BluetoothDiscoverySession
> discovery_session
) {
178 PA_LOG(INFO
) << "Discovery session started";
179 discovery_session_
= discovery_session
.Pass();
182 void BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError() {
183 PA_LOG(WARNING
) << "Error starting discovery session";
186 void BluetoothLowEnergyConnectionFinder::StartDiscoverySession() {
188 if (discovery_session_
&& discovery_session_
->IsActive()) {
189 PA_LOG(INFO
) << "Discovery session already active";
193 // Discover only low energy (LE) devices with strong enough signal.
194 scoped_ptr
<BluetoothDiscoveryFilter
> filter(new BluetoothDiscoveryFilter(
195 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE
));
196 filter
->SetRSSI(kMinDiscoveryRSSI
);
198 adapter_
->StartDiscoverySessionWithFilter(
200 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted
,
201 weak_ptr_factory_
.GetWeakPtr()),
203 &BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError
,
204 weak_ptr_factory_
.GetWeakPtr()));
207 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped() {
208 PA_LOG(INFO
) << "Discovery session stopped";
209 discovery_session_
.reset();
212 void BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError() {
213 PA_LOG(WARNING
) << "Error stopping discovery session";
216 void BluetoothLowEnergyConnectionFinder::StopDiscoverySession() {
217 PA_LOG(INFO
) << "Stopping discovery sesison";
220 PA_LOG(WARNING
) << "Adapter not initialized";
223 if (!discovery_session_
|| !discovery_session_
->IsActive()) {
224 PA_LOG(INFO
) << "No Active discovery session";
228 discovery_session_
->Stop(
229 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped
,
230 weak_ptr_factory_
.GetWeakPtr()),
232 &BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError
,
233 weak_ptr_factory_
.GetWeakPtr()));
236 bool BluetoothLowEnergyConnectionFinder::HasService(
237 BluetoothDevice
* remote_device
) {
239 PA_LOG(INFO
) << "Device " << remote_device
->GetAddress() << " has "
240 << remote_device
->GetUUIDs().size() << " services.";
241 std::vector
<device::BluetoothUUID
> uuids
= remote_device
->GetUUIDs();
242 for (const auto& service_uuid
: uuids
) {
243 if (remote_service_uuid_
== service_uuid
) {
251 void BluetoothLowEnergyConnectionFinder::OnCreateGattConnectionError(
252 std::string device_address
,
253 BluetoothDevice::ConnectErrorCode error_code
) {
254 PA_LOG(WARNING
) << "Error creating connection to device " << device_address
255 << " : error code = " << error_code
;
257 BluetoothDevice
* device
= GetDevice(device_address
);
258 const auto& i
= pending_connections_
.find(device
);
259 if (i
!= pending_connections_
.end()) {
260 PA_LOG(INFO
) << "Remove pending connection to " << device
->GetAddress();
261 pending_connections_
.erase(i
);
265 void BluetoothLowEnergyConnectionFinder::OnGattConnectionCreated(
266 scoped_ptr
<BluetoothGattConnection
> gatt_connection
) {
268 CloseGattConnection(gatt_connection
.Pass());
272 PA_LOG(INFO
) << "GATT connection created";
274 pending_connections_
.clear();
276 gatt_connection_
= gatt_connection
.Pass();
278 // This is a workaround for crbug.com/498850. Currently, trying to write/read
279 // characteristics immediatelly after the GATT connection was established
280 // fails with the very informative GATT_ERROR_FAILED.
281 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
283 base::Bind(&BluetoothLowEnergyConnectionFinder::CompleteConnection
,
284 weak_ptr_factory_
.GetWeakPtr()),
285 delay_after_gatt_connection_
);
287 StopDiscoverySession();
290 void BluetoothLowEnergyConnectionFinder::CompleteConnection() {
291 connection_
= CreateConnection(gatt_connection_
.Pass());
292 connection_
->AddObserver(this);
293 connection_
->Connect();
296 void BluetoothLowEnergyConnectionFinder::CreateGattConnection(
297 device::BluetoothDevice
* remote_device
) {
298 PA_LOG(INFO
) << "SmartLock service found ("
299 << remote_service_uuid_
.canonical_value() << ")\n"
300 << "device = " << remote_device
->GetAddress()
301 << ", name = " << remote_device
->GetName();
302 remote_device
->CreateGattConnection(
303 base::Bind(&BluetoothLowEnergyConnectionFinder::OnGattConnectionCreated
,
304 weak_ptr_factory_
.GetWeakPtr()),
306 &BluetoothLowEnergyConnectionFinder::OnCreateGattConnectionError
,
307 weak_ptr_factory_
.GetWeakPtr(), remote_device
->GetAddress()));
310 void BluetoothLowEnergyConnectionFinder::CloseGattConnection(
311 scoped_ptr
<device::BluetoothGattConnection
> gatt_connection
) {
312 DCHECK(gatt_connection
);
314 // Destroying the BluetoothGattConnection also disconnects it.
315 gatt_connection
.reset();
318 scoped_ptr
<Connection
> BluetoothLowEnergyConnectionFinder::CreateConnection(
319 scoped_ptr
<BluetoothGattConnection
> gatt_connection
) {
320 remote_device_
.bluetooth_address
= gatt_connection
->GetDeviceAddress();
322 return make_scoped_ptr(new BluetoothLowEnergyConnection(
323 remote_device_
, adapter_
, remote_service_uuid_
, to_peripheral_char_uuid_
,
324 from_peripheral_char_uuid_
, gatt_connection
.Pass(),
325 max_number_of_tries_
));
328 void BluetoothLowEnergyConnectionFinder::OnConnectionStatusChanged(
329 Connection
* connection
,
330 Connection::Status old_status
,
331 Connection::Status new_status
) {
332 DCHECK_EQ(connection
, connection_
.get());
334 if (!connection_callback_
.is_null() && connection_
->IsConnected()) {
335 adapter_
->RemoveObserver(this);
336 connection_
->RemoveObserver(this);
338 // Note: any observer of |connection_| added in |connection_callback_| will
339 // also receive this |OnConnectionStatusChanged| notification (IN_PROGRESS
341 connection_callback_
.Run(connection_
.Pass());
342 connection_callback_
.Reset();
343 } else if (old_status
== Connection::IN_PROGRESS
) {
344 PA_LOG(WARNING
) << "Connection failed. Retrying.";
345 RestartDiscoverySessionWhenReady();
349 void BluetoothLowEnergyConnectionFinder::RestartDiscoverySessionWhenReady() {
350 // To restart scanning for devices, it's necessary to ensure that:
351 // (i) the GATT connection to |remove_device_| is closed;
352 // (ii) there is no pending call to
353 // |device::BluetoothDiscoverySession::Stop()|.
354 // The second condition is satisfied when |OnDiscoveryStopped| is called and
355 // |discovery_session_| is reset.
356 if ((!gatt_connection_
|| !gatt_connection_
->IsConnected()) &&
357 !discovery_session_
) {
360 StartDiscoverySession();
362 base::ThreadTaskRunnerHandle::Get()->PostTask(
363 FROM_HERE
, base::Bind(&BluetoothLowEnergyConnectionFinder::
364 RestartDiscoverySessionWhenReady
,
365 weak_ptr_factory_
.GetWeakPtr()));
369 void BluetoothLowEnergyConnectionFinder::SetDelayForTesting(
370 base::TimeDelta delay
) {
371 delay_after_gatt_connection_
= delay
;
374 BluetoothDevice
* BluetoothLowEnergyConnectionFinder::GetDevice(
375 std::string device_address
) {
376 // It's not possible to simply use
377 // |adapter_->GetDevice(GetRemoteDeviceAddress())| to find the device with MAC
378 // address |GetRemoteDeviceAddress()|. For paired devices,
379 // BluetoothAdapter::GetDevice(XXX) searches for the temporary MAC address
380 // XXX, whereas |remote_device_.bluetooth_address| is the real MAC address.
381 // This is a bug in the way device::BluetoothAdapter is storing the devices
382 // (see crbug.com/497841).
383 std::vector
<BluetoothDevice
*> devices
= adapter_
->GetDevices();
384 for (const auto& device
: devices
) {
385 if (device
->GetAddress() == device_address
)
391 } // namespace proximity_auth