Don't preload rarely seen large images
[chromium-blink-merge.git] / components / proximity_auth / ble / bluetooth_low_energy_connection_finder.cc
blob37276e87eacb44c16975ff768b5c9e867756fe6c
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/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 {
29 namespace {
30 const int kMinDiscoveryRSSI = -100;
31 const int kDelayAfterGattConnectionMilliseconds = 1000;
32 } // namespace
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)),
43 connected_(false),
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();
55 if (connection_) {
56 connection_->RemoveObserver(this);
57 connection_.reset();
60 if (adapter_) {
61 adapter_->RemoveObserver(this);
62 adapter_ = NULL;
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.";
70 return;
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,
85 bool powered) {
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
91 // filter.
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);
99 DCHECK(device);
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);
115 DCHECK(device);
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) {
129 if (connected_)
130 return;
131 const auto& i = pending_connections_.find(device);
132 if (i != pending_connections_.end()) {
133 PA_LOG(INFO) << "Pending connection to device " << device->GetAddress();
134 return;
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) {
146 if (connected_)
147 return;
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";
160 adapter_ = adapter;
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() {
187 DCHECK(adapter_);
188 if (discovery_session_ && discovery_session_->IsActive()) {
189 PA_LOG(INFO) << "Discovery session already active";
190 return;
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(
199 filter.Pass(),
200 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStarted,
201 weak_ptr_factory_.GetWeakPtr()),
202 base::Bind(
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";
219 if (!adapter_) {
220 PA_LOG(WARNING) << "Adapter not initialized";
221 return;
223 if (!discovery_session_ || !discovery_session_->IsActive()) {
224 PA_LOG(INFO) << "No Active discovery session";
225 return;
228 discovery_session_->Stop(
229 base::Bind(&BluetoothLowEnergyConnectionFinder::OnDiscoverySessionStopped,
230 weak_ptr_factory_.GetWeakPtr()),
231 base::Bind(
232 &BluetoothLowEnergyConnectionFinder::OnStopDiscoverySessionError,
233 weak_ptr_factory_.GetWeakPtr()));
236 bool BluetoothLowEnergyConnectionFinder::HasService(
237 BluetoothDevice* remote_device) {
238 if (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) {
244 return true;
248 return false;
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) {
267 if (connected_) {
268 CloseGattConnection(gatt_connection.Pass());
269 return;
272 PA_LOG(INFO) << "GATT connection created";
273 connected_ = true;
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(
282 FROM_HERE,
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()),
305 base::Bind(
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
340 // -> CONNECTED).
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_) {
358 connection_.reset();
359 connected_ = false;
360 StartDiscoverySession();
361 } else {
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)
386 return device;
388 return nullptr;
391 } // namespace proximity_auth