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/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
{
31 const int kMinDiscoveryRSSI
= -90;
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();
60 connection_
->RemoveObserver(this);
65 adapter_
->RemoveObserver(this);
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.";
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
,
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
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
);
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
);
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|.
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
) {
155 // TODO(sacomoto): Remove it when ProximityAuthBleSystem is not needed
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
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
) {
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
) {
185 void BluetoothLowEnergyConnectionFinder::OnAdapterInitialized(
186 scoped_refptr
<BluetoothAdapter
> adapter
) {
187 PA_LOG(INFO
) << "Adapter ready";
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() {
211 if (discovery_session_
&& discovery_session_
->IsActive()) {
212 PA_LOG(INFO
) << "Discovery session already active";
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(
223 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted
,
224 weak_ptr_factory_
.GetWeakPtr()),
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";
243 PA_LOG(WARNING
) << "Adapter not initialized";
246 if (!discovery_session_
|| !discovery_session_
->IsActive()) {
247 PA_LOG(INFO
) << "No Active discovery session";
251 discovery_session_
->Stop(
252 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped
,
253 weak_ptr_factory_
.GetWeakPtr()),
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
<< " -> "
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(
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.";
307 StartDiscoverySession();
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
)
333 void BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync() {
334 connection_callback_
.Run(connection_
.Pass());
337 } // namespace proximity_auth