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/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
= -90;
33 class BluetoothThrottler
;
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 BluetoothThrottler
* bluetooth_throttler
,
41 int max_number_of_tries
)
42 : remote_service_uuid_(device::BluetoothUUID(remote_service_uuid
)),
43 to_peripheral_char_uuid_(device::BluetoothUUID(to_peripheral_char_uuid
)),
44 from_peripheral_char_uuid_(
45 device::BluetoothUUID(from_peripheral_char_uuid
)),
46 device_whitelist_(device_whitelist
),
47 bluetooth_throttler_(bluetooth_throttler
),
48 max_number_of_tries_(max_number_of_tries
),
49 weak_ptr_factory_(this) {}
51 BluetoothLowEnergyConnectionFinder::~BluetoothLowEnergyConnectionFinder() {
52 if (discovery_session_
) {
53 StopDiscoverySession();
57 connection_
->RemoveObserver(this);
62 adapter_
->RemoveObserver(this);
67 void BluetoothLowEnergyConnectionFinder::Find(
68 const ConnectionCallback
& connection_callback
) {
69 if (!device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable()) {
70 PA_LOG(WARNING
) << "Bluetooth is unsupported on this platform. Aborting.";
73 PA_LOG(INFO
) << "Finding connection";
75 connection_callback_
= connection_callback
;
77 device::BluetoothAdapterFactory::GetAdapter(
78 base::Bind(&BluetoothLowEnergyConnectionFinder::OnAdapterInitialized
,
79 weak_ptr_factory_
.GetWeakPtr()));
82 // It's not necessary to observe |AdapterPresentChanged| too. When |adapter_| is
83 // present, but not powered, it's not possible to scan for new devices.
84 void BluetoothLowEnergyConnectionFinder::AdapterPoweredChanged(
85 BluetoothAdapter
* adapter
,
87 DCHECK_EQ(adapter_
.get(), adapter
);
88 PA_LOG(INFO
) << "Adapter powered: " << powered
;
90 // Important: do not rely on |adapter->IsDiscoverying()| to verify if there is
91 // an active discovery session. We need to create our own with an specific
93 if (powered
&& (!discovery_session_
|| !discovery_session_
->IsActive()))
94 StartDiscoverySession();
97 void BluetoothLowEnergyConnectionFinder::DeviceAdded(BluetoothAdapter
* adapter
,
98 BluetoothDevice
* device
) {
99 DCHECK_EQ(adapter_
.get(), adapter
);
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
);
117 // Note: Only consider |device| when it was actually added/updated during a
118 // scanning, otherwise the device is stale and the GATT connection will fail.
119 // For instance, when |adapter_| change status from unpowered to powered,
120 // |DeviceAdded| is called for each paired |device|.
121 if (adapter_
->IsPowered() && discovery_session_
&&
122 discovery_session_
->IsActive())
123 HandleDeviceUpdated(device
);
126 void BluetoothLowEnergyConnectionFinder::HandleDeviceUpdated(
127 BluetoothDevice
* device
) {
128 // Ensuring only one call to |CreateConnection()| is made. A new |connection_|
129 // can be created only when the previous one disconnects, triggering a call to
130 // |OnConnectionStatusChanged|.
131 if (connection_
|| !device
->IsPaired())
134 if (HasService(device
) ||
135 device_whitelist_
->HasDeviceWithAddress(device
->GetAddress())) {
136 PA_LOG(INFO
) << "Connecting to paired device " << device
->GetAddress()
137 << " with service (" << HasService(device
)
138 << ") or is whitelisted ("
139 << device_whitelist_
->HasDeviceWithAddress(
140 device
->GetAddress()) << ")";
142 connection_
= CreateConnection(device
->GetAddress());
143 connection_
->AddObserver(this);
144 connection_
->Connect();
146 StopDiscoverySession();
150 void BluetoothLowEnergyConnectionFinder::OnAdapterInitialized(
151 scoped_refptr
<BluetoothAdapter
> adapter
) {
152 PA_LOG(INFO
) << "Adapter ready";
155 adapter_
->AddObserver(this);
157 // Note: it's not possible to connect with the paired directly, as the
158 // temporary MAC may not be resolved automatically (see crbug.com/495402). The
159 // Bluetooth adapter will fire |OnDeviceChanged| notifications for all
160 // Bluetooth Low Energy devices that are advertising.
161 StartDiscoverySession();
164 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted(
165 scoped_ptr
<device::BluetoothDiscoverySession
> discovery_session
) {
166 PA_LOG(INFO
) << "Discovery session started";
167 discovery_session_
= discovery_session
.Pass();
170 void BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError() {
171 PA_LOG(WARNING
) << "Error starting discovery session";
174 void BluetoothLowEnergyConnectionFinder::StartDiscoverySession() {
176 if (discovery_session_
&& discovery_session_
->IsActive()) {
177 PA_LOG(INFO
) << "Discovery session already active";
181 // Discover only low energy (LE) devices with strong enough signal.
182 scoped_ptr
<BluetoothDiscoveryFilter
> filter(new BluetoothDiscoveryFilter(
183 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE
));
184 filter
->SetRSSI(kMinDiscoveryRSSI
);
186 adapter_
->StartDiscoverySessionWithFilter(
188 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted
,
189 weak_ptr_factory_
.GetWeakPtr()),
191 &BluetoothLowEnergyConnectionFinder::OnStartDiscoverySessionError
,
192 weak_ptr_factory_
.GetWeakPtr()));
195 void BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped() {
196 PA_LOG(INFO
) << "Discovery session stopped";
197 discovery_session_
.reset();
200 void BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError() {
201 PA_LOG(WARNING
) << "Error stopping discovery session";
204 void BluetoothLowEnergyConnectionFinder::StopDiscoverySession() {
205 PA_LOG(INFO
) << "Stopping discovery sesison";
208 PA_LOG(WARNING
) << "Adapter not initialized";
211 if (!discovery_session_
|| !discovery_session_
->IsActive()) {
212 PA_LOG(INFO
) << "No Active discovery session";
216 discovery_session_
->Stop(
217 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped
,
218 weak_ptr_factory_
.GetWeakPtr()),
220 &BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError
,
221 weak_ptr_factory_
.GetWeakPtr()));
224 bool BluetoothLowEnergyConnectionFinder::HasService(
225 BluetoothDevice
* remote_device
) {
227 PA_LOG(INFO
) << "Device " << remote_device
->GetAddress() << " has "
228 << remote_device
->GetUUIDs().size() << " services.";
229 std::vector
<device::BluetoothUUID
> uuids
= remote_device
->GetUUIDs();
230 for (const auto& service_uuid
: uuids
) {
231 if (remote_service_uuid_
== service_uuid
) {
239 scoped_ptr
<Connection
> BluetoothLowEnergyConnectionFinder::CreateConnection(
240 const std::string
& device_address
) {
241 RemoteDevice
remote_device(std::string(), std::string(), device_address
,
244 return make_scoped_ptr(new BluetoothLowEnergyConnection(
245 remote_device
, adapter_
, remote_service_uuid_
, to_peripheral_char_uuid_
,
246 from_peripheral_char_uuid_
, bluetooth_throttler_
, max_number_of_tries_
));
249 void BluetoothLowEnergyConnectionFinder::OnConnectionStatusChanged(
250 Connection
* connection
,
251 Connection::Status old_status
,
252 Connection::Status new_status
) {
253 DCHECK_EQ(connection
, connection_
.get());
254 PA_LOG(INFO
) << "OnConnectionStatusChanged: " << old_status
<< " -> "
257 if (!connection_callback_
.is_null() && connection_
->IsConnected()) {
258 adapter_
->RemoveObserver(this);
259 connection_
->RemoveObserver(this);
261 // If we invoke the callback now, the callback function may install its own
262 // observer to |connection_|. Because we are in the ConnectionObserver
263 // callstack, this new observer will receive this connection event.
264 // Therefore, we need to invoke the callback asynchronously.
265 base::ThreadTaskRunnerHandle::Get()->PostTask(
267 base::Bind(&BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync
,
268 weak_ptr_factory_
.GetWeakPtr()));
269 } else if (old_status
== Connection::IN_PROGRESS
) {
270 PA_LOG(WARNING
) << "Connection failed. Retrying.";
271 RestartDiscoverySessionWhenReady();
275 void BluetoothLowEnergyConnectionFinder::RestartDiscoverySessionWhenReady() {
276 PA_LOG(INFO
) << "Trying to restart discovery.";
278 // To restart scanning for devices, it's necessary to ensure that:
279 // (i) the GATT connection to |remove_device_| is closed;
280 // (ii) there is no pending call to
281 // |device::BluetoothDiscoverySession::Stop()|.
282 // The second condition is satisfied when |OnDiscoveryStopped| is called and
283 // |discovery_session_| is reset.
284 if (!discovery_session_
) {
285 PA_LOG(INFO
) << "Ready to start discovery.";
287 StartDiscoverySession();
289 base::ThreadTaskRunnerHandle::Get()->PostTask(
290 FROM_HERE
, base::Bind(&BluetoothLowEnergyConnectionFinder::
291 RestartDiscoverySessionWhenReady
,
292 weak_ptr_factory_
.GetWeakPtr()));
296 BluetoothDevice
* BluetoothLowEnergyConnectionFinder::GetDevice(
297 std::string device_address
) {
298 // It's not possible to simply use
299 // |adapter_->GetDevice(GetRemoteDeviceAddress())| to find the device with MAC
300 // address |GetRemoteDeviceAddress()|. For paired devices,
301 // BluetoothAdapter::GetDevice(XXX) searches for the temporary MAC address
302 // XXX, whereas |remote_device_.bluetooth_address| is the real MAC address.
303 // This is a bug in the way device::BluetoothAdapter is storing the devices
304 // (see crbug.com/497841).
305 std::vector
<BluetoothDevice
*> devices
= adapter_
->GetDevices();
306 for (const auto& device
: devices
) {
307 if (device
->GetAddress() == device_address
)
313 void BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync() {
314 connection_callback_
.Run(connection_
.Pass());
317 } // namespace proximity_auth