summaryrefslogtreecommitdiffstats
path: root/components/proximity_auth
diff options
context:
space:
mode:
authortengs <tengs@chromium.org>2015-08-10 17:21:01 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-11 00:21:37 +0000
commitd740523e5f417fb38ee37b9dcb322e79cd83e736 (patch)
tree14dc1b2c232232660c2f5619aa82598d8f846b19 /components/proximity_auth
parentbde397b5f4db6102eba9540f77c61d3942556a36 (diff)
downloadchromium_src-d740523e5f417fb38ee37b9dcb322e79cd83e736.zip
chromium_src-d740523e5f417fb38ee37b9dcb322e79cd83e736.tar.gz
chromium_src-d740523e5f417fb38ee37b9dcb322e79cd83e736.tar.bz2
Implement finding BLE connections in chrome://proximity-auth.
This CL also refactors the classic Bluetooth connection finder to use SeekDeviceByAddress in order to share the ConnectionFinder interface in ProximityAuthWebUIHandler. BUG=517641 TEST=manual and unit tests Review URL: https://codereview.chromium.org/1277483007 Cr-Commit-Position: refs/heads/master@{#342747}
Diffstat (limited to 'components/proximity_auth')
-rw-r--r--components/proximity_auth/ble/bluetooth_low_energy_connection_finder.cc23
-rw-r--r--components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h4
-rw-r--r--components/proximity_auth/ble/bluetooth_low_energy_connection_finder_unittest.cc6
-rw-r--r--components/proximity_auth/bluetooth_connection_finder.cc81
-rw-r--r--components/proximity_auth/bluetooth_connection_finder.h22
-rw-r--r--components/proximity_auth/bluetooth_connection_finder_unittest.cc196
-rw-r--r--components/proximity_auth/webui/proximity_auth_webui_handler.cc165
-rw-r--r--components/proximity_auth/webui/proximity_auth_webui_handler.h23
8 files changed, 379 insertions, 141 deletions
diff --git a/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.cc b/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.cc
index 420500f..9513f99 100644
--- a/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.cc
+++ b/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.cc
@@ -114,15 +114,17 @@ void BluetoothLowEnergyConnectionFinder::DeviceChanged(
BluetoothDevice* device) {
DCHECK_EQ(adapter_.get(), adapter);
DCHECK(device);
- PA_LOG(INFO) << "Device changed: " << device->GetAddress();
// Note: Only consider |device| when it was actually added/updated during a
// scanning, otherwise the device is stale and the GATT connection will fail.
// For instance, when |adapter_| change status from unpowered to powered,
// |DeviceAdded| is called for each paired |device|.
if (adapter_->IsPowered() && discovery_session_ &&
- discovery_session_->IsActive())
+ discovery_session_->IsActive()) {
+ if (device_whitelist_->HasDeviceWithAddress(device->GetAddress()))
+ PA_LOG(INFO) << "Whitelisted device changed: " << device->GetAddress();
HandleDeviceUpdated(device);
+ }
}
void BluetoothLowEnergyConnectionFinder::HandleDeviceUpdated(
@@ -266,11 +268,14 @@ void BluetoothLowEnergyConnectionFinder::OnConnectionStatusChanged(
adapter_->RemoveObserver(this);
connection_->RemoveObserver(this);
- // Note: any observer of |connection_| added in |connection_callback_| will
- // also receive this |OnConnectionStatusChanged| notification (IN_PROGRESS
- // -> CONNECTED).
- connection_callback_.Run(connection_.Pass());
- connection_callback_.Reset();
+ // If we invoke the callback now, the callback function may install its own
+ // observer to |connection_|. Because we are in the ConnectionObserver
+ // callstack, this new observer will receive this connection event.
+ // Therefore, we need to invoke the callback asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync,
+ weak_ptr_factory_.GetWeakPtr()));
} else if (old_status == Connection::IN_PROGRESS) {
PA_LOG(WARNING) << "Connection failed. Retrying.";
RestartDiscoverySessionWhenReady();
@@ -315,4 +320,8 @@ BluetoothDevice* BluetoothLowEnergyConnectionFinder::GetDevice(
return nullptr;
}
+void BluetoothLowEnergyConnectionFinder::InvokeCallbackAsync() {
+ connection_callback_.Run(connection_.Pass());
+}
+
} // namespace proximity_auth
diff --git a/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h b/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h
index 5d4cc52..9c76a48 100644
--- a/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h
+++ b/components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h
@@ -98,6 +98,10 @@ class BluetoothLowEnergyConnectionFinder
// Restarts the discovery session after creating |connection_| fails.
void RestartDiscoverySessionWhenReady();
+ // Used to invoke |connection_callback_| asynchronously, decoupling the
+ // callback invocation from the ConnectionObserver callstack.
+ void InvokeCallbackAsync();
+
// Returns the device with |device_address|.
device::BluetoothDevice* GetDevice(std::string device_address);
diff --git a/components/proximity_auth/ble/bluetooth_low_energy_connection_finder_unittest.cc b/components/proximity_auth/ble/bluetooth_low_energy_connection_finder_unittest.cc
index b41ec03..4cb745c 100644
--- a/components/proximity_auth/ble/bluetooth_low_energy_connection_finder_unittest.cc
+++ b/components/proximity_auth/ble/bluetooth_low_energy_connection_finder_unittest.cc
@@ -366,9 +366,11 @@ TEST_F(ProximityAuthBluetoothLowEnergyConnectionFinderTest,
connection_finder.DeviceAdded(adapter_.get(), device_.get());
// Creating a connection.
+ base::RunLoop run_loop;
EXPECT_FALSE(last_found_connection_);
connection->SetStatus(Connection::IN_PROGRESS);
connection->SetStatus(Connection::CONNECTED);
+ run_loop.RunUntilIdle();
EXPECT_TRUE(last_found_connection_);
}
@@ -424,9 +426,11 @@ TEST_F(ProximityAuthBluetoothLowEnergyConnectionFinderTest,
EXPECT_CALL(*last_discovery_session_alias_, Stop(_, _)).Times(AtLeast(1));
// Completing the connection.
+ base::RunLoop run_loop;
EXPECT_FALSE(last_found_connection_);
connection->SetStatus(Connection::IN_PROGRESS);
connection->SetStatus(Connection::CONNECTED);
+ run_loop.RunUntilIdle();
EXPECT_TRUE(last_found_connection_);
}
@@ -475,9 +479,11 @@ TEST_F(ProximityAuthBluetoothLowEnergyConnectionFinderTest,
EXPECT_CALL(*last_discovery_session_alias_, Stop(_, _)).Times(AtLeast(1));
// Completing the connection.
+ base::RunLoop run_loop;
ASSERT_FALSE(last_found_connection_);
connection->SetStatus(Connection::IN_PROGRESS);
connection->SetStatus(Connection::CONNECTED);
+ run_loop.RunUntilIdle();
EXPECT_TRUE(last_found_connection_);
}
diff --git a/components/proximity_auth/bluetooth_connection_finder.cc b/components/proximity_auth/bluetooth_connection_finder.cc
index 7596a11..00b5146 100644
--- a/components/proximity_auth/bluetooth_connection_finder.cc
+++ b/components/proximity_auth/bluetooth_connection_finder.cc
@@ -54,6 +54,15 @@ scoped_ptr<Connection> BluetoothConnectionFinder::CreateConnection() {
return scoped_ptr<Connection>(new BluetoothConnection(remote_device_, uuid_));
}
+void BluetoothConnectionFinder::SeekDeviceByAddress(
+ const std::string& bluetooth_address,
+ const base::Closure& callback,
+ const bluetooth_util::ErrorCallback& error_callback) {
+ bluetooth_util::SeekDeviceByAddress(
+ bluetooth_address, callback, error_callback,
+ base::ThreadTaskRunnerHandle::Get().get());
+}
+
bool BluetoothConnectionFinder::IsReadyToPoll() {
bool is_adapter_available =
adapter_.get() && adapter_->IsPresent() && adapter_->IsPowered();
@@ -77,19 +86,62 @@ void BluetoothConnectionFinder::PollIfReady() {
if (connection_)
return;
- PA_LOG(INFO) << "Polling for connection...";
- connection_ = CreateConnection();
- connection_->AddObserver(this);
- connection_->Connect();
+ // This SeekDeviceByAddress operation is needed to connect to a device if
+ // it is not already known to the adapter.
+ if (!adapter_->GetDevice(remote_device_.bluetooth_address)) {
+ PA_LOG(INFO) << "Remote device [" << remote_device_.bluetooth_address
+ << "] is not known. "
+ << "Seeking device directly by address...";
+
+ SeekDeviceByAddress(
+ remote_device_.bluetooth_address,
+ base::Bind(&BluetoothConnectionFinder::OnSeekedDeviceByAddress,
+ weak_ptr_factory_.GetWeakPtr()),
+ base::Bind(&BluetoothConnectionFinder::OnSeekedDeviceByAddressError,
+ weak_ptr_factory_.GetWeakPtr()));
+ } else {
+ PA_LOG(INFO) << "Remote device known, connecting...";
+ connection_ = CreateConnection();
+ connection_->AddObserver(this);
+ connection_->Connect();
+ }
+}
+
+void BluetoothConnectionFinder::PostDelayedPoll() {
+ if (has_delayed_poll_scheduled_) {
+ PA_LOG(WARNING) << "Delayed poll already scheduled, skipping.";
+ return;
+ }
+
+ PA_LOG(INFO) << "Posting delayed poll..";
+ has_delayed_poll_scheduled_ = true;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&BluetoothConnectionFinder::OnDelayedPoll,
+ weak_ptr_factory_.GetWeakPtr()),
+ polling_interval_);
}
-void BluetoothConnectionFinder::DelayedPollIfReady() {
+void BluetoothConnectionFinder::OnDelayedPoll() {
// Note that there is no longer a pending task, and therefore polling is
// permitted.
has_delayed_poll_scheduled_ = false;
PollIfReady();
}
+void BluetoothConnectionFinder::OnSeekedDeviceByAddress() {
+ // Sanity check that the remote device is now known by the adapter.
+ if (adapter_->GetDevice(remote_device_.bluetooth_address))
+ PollIfReady();
+ else
+ PostDelayedPoll();
+}
+
+void BluetoothConnectionFinder::OnSeekedDeviceByAddressError(
+ const std::string& error_message) {
+ PA_LOG(ERROR) << "Failed to seek device: " << error_message;
+ PostDelayedPoll();
+}
+
void BluetoothConnectionFinder::UnregisterAsObserver() {
if (connection_) {
connection_->RemoveObserver(this);
@@ -131,17 +183,24 @@ void BluetoothConnectionFinder::OnConnectionStatusChanged(
PA_LOG(WARNING) << "Connection found! Elapsed Time: "
<< elapsed.InMilliseconds() << "ms.";
UnregisterAsObserver();
- connection_callback_.Run(connection_.Pass());
+
+ // If we invoke the callback now, the callback function may install its own
+ // observer to |connection_|. Because we are in the ConnectionObserver
+ // callstack, this new observer will receive this connection event.
+ // Therefore, we need to invoke the callback asynchronously.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&BluetoothConnectionFinder::InvokeCallbackAsync,
+ weak_ptr_factory_.GetWeakPtr()));
} else if (old_status == Connection::IN_PROGRESS) {
PA_LOG(WARNING)
<< "Connection failed! Scheduling another polling iteration.";
connection_.reset();
- has_delayed_poll_scheduled_ = true;
- base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
- FROM_HERE, base::Bind(&BluetoothConnectionFinder::DelayedPollIfReady,
- weak_ptr_factory_.GetWeakPtr()),
- polling_interval_);
+ PostDelayedPoll();
}
}
+void BluetoothConnectionFinder::InvokeCallbackAsync() {
+ connection_callback_.Run(connection_.Pass());
+}
+
} // namespace proximity_auth
diff --git a/components/proximity_auth/bluetooth_connection_finder.h b/components/proximity_auth/bluetooth_connection_finder.h
index cca88f63..ac195d8 100644
--- a/components/proximity_auth/bluetooth_connection_finder.h
+++ b/components/proximity_auth/bluetooth_connection_finder.h
@@ -11,6 +11,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
+#include "components/proximity_auth/bluetooth_util.h"
#include "components/proximity_auth/connection_finder.h"
#include "components/proximity_auth/connection_observer.h"
#include "components/proximity_auth/remote_device.h"
@@ -39,6 +40,13 @@ class BluetoothConnectionFinder : public ConnectionFinder,
// Exposed for mocking out the connection in tests.
virtual scoped_ptr<Connection> CreateConnection();
+ // Calls bluetooth_util::SeekDeviceByAddress. Exposed for testing, as this
+ // utility function is platform dependent.
+ virtual void SeekDeviceByAddress(
+ const std::string& bluetooth_address,
+ const base::Closure& callback,
+ const bluetooth_util::ErrorCallback& error_callback);
+
// BluetoothAdapter::Observer:
void AdapterPresentChanged(device::BluetoothAdapter* adapter,
bool present) override;
@@ -53,8 +61,14 @@ class BluetoothConnectionFinder : public ConnectionFinder,
// another iteration of polling.
void PollIfReady();
- // Wrapper around |PollIfReady()| that can be posted as a delayed task.
- void DelayedPollIfReady();
+ // Posts a delayed task to call |PollIfReady|. |OnDelayedPoll()| will be
+ // called when the task fires.
+ void PostDelayedPoll();
+ void OnDelayedPoll();
+
+ // Callbacks for bluetooth_util::SeekDeviceByAddress().
+ void OnSeekedDeviceByAddress();
+ void OnSeekedDeviceByAddressError(const std::string& error_message);
// Unregisters |this| instance as an observer from all objects that it might
// have registered with.
@@ -68,6 +82,10 @@ class BluetoothConnectionFinder : public ConnectionFinder,
Connection::Status old_status,
Connection::Status new_status) override;
+ // Used to invoke |connection_callback_| asynchronously, decoupling the
+ // callback invocation from the ConnectionObserver callstack.
+ void InvokeCallbackAsync();
+
// The remote device to connect to.
const RemoteDevice remote_device_;
diff --git a/components/proximity_auth/bluetooth_connection_finder_unittest.cc b/components/proximity_auth/bluetooth_connection_finder_unittest.cc
index ac6b38c..83d930c 100644
--- a/components/proximity_auth/bluetooth_connection_finder_unittest.cc
+++ b/components/proximity_auth/bluetooth_connection_finder_unittest.cc
@@ -15,6 +15,7 @@
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_uuid.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -80,12 +81,35 @@ class MockBluetoothConnectionFinder : public BluetoothConnectionFinder {
using BluetoothConnectionFinder::AdapterPresentChanged;
using BluetoothConnectionFinder::AdapterPoweredChanged;
+ void ClearSeekCallbacks() {
+ seek_callback_ = base::Closure();
+ seek_error_callback_ = bluetooth_util::ErrorCallback();
+ }
+
+ const base::Closure& seek_callback() { return seek_callback_; }
+ const bluetooth_util::ErrorCallback& seek_error_callback() {
+ return seek_error_callback_;
+ }
+
protected:
+ // BluetoothConnectionFinder:
scoped_ptr<Connection> CreateConnection() override {
return make_scoped_ptr(CreateConnectionProxy());
}
+ void SeekDeviceByAddress(
+ const std::string& bluetooth_address,
+ const base::Closure& callback,
+ const bluetooth_util::ErrorCallback& error_callback) override {
+ EXPECT_EQ(kBluetoothAddress, bluetooth_address);
+ seek_callback_ = callback;
+ seek_error_callback_ = error_callback;
+ }
+
private:
+ base::Closure seek_callback_;
+ bluetooth_util::ErrorCallback seek_error_callback_;
+
DISALLOW_COPY_AND_ASSIGN(MockBluetoothConnectionFinder);
};
@@ -95,6 +119,13 @@ class ProximityAuthBluetoothConnectionFinderTest : public testing::Test {
protected:
ProximityAuthBluetoothConnectionFinderTest()
: adapter_(new NiceMock<device::MockBluetoothAdapter>),
+ bluetooth_device_(new NiceMock<device::MockBluetoothDevice>(
+ adapter_.get(),
+ device::BluetoothDevice::DEVICE_PHONE,
+ kDeviceName,
+ kBluetoothAddress,
+ true,
+ false)),
connection_callback_(base::Bind(
&ProximityAuthBluetoothConnectionFinderTest::OnConnectionFound,
base::Unretained(this))) {
@@ -104,6 +135,11 @@ class ProximityAuthBluetoothConnectionFinderTest : public testing::Test {
// can override this as needed.
ON_CALL(*adapter_, IsPresent()).WillByDefault(Return(true));
ON_CALL(*adapter_, IsPowered()).WillByDefault(Return(true));
+
+ // By default, the remote device is known to |adapter_| so
+ // |SeekDeviceByAddress()| will not be called.
+ ON_CALL(*adapter_, GetDevice(kBluetoothAddress))
+ .WillByDefault(Return(bluetooth_device_.get()));
}
MOCK_METHOD1(OnConnectionFoundProxy, void(Connection* connection));
@@ -112,7 +148,29 @@ class ProximityAuthBluetoothConnectionFinderTest : public testing::Test {
last_found_connection_ = connection.Pass();
}
+ // Starts |connection_finder_|. If |expect_connection| is true, then we set an
+ // expectation that an in-progress connection will be created and returned.
+ MockConnection* StartConnectionFinder(bool expect_connection) {
+ MockConnection* connection = nullptr;
+ if (expect_connection)
+ connection = connection_finder_.ExpectCreateConnection();
+ connection_finder_.Find(connection_callback_);
+ return connection;
+ }
+
+ // Given an in-progress |connection| returned by |StartConnectionFinder()|,
+ // simulate it transitioning to the CONNECTED state.
+ void SimulateDeviceConnection(MockConnection* connection) {
+ connection->SetStatus(Connection::IN_PROGRESS);
+ base::RunLoop run_loop;
+ EXPECT_CALL(*this, OnConnectionFoundProxy(_));
+ connection->SetStatus(Connection::CONNECTED);
+ run_loop.RunUntilIdle();
+ }
+
scoped_refptr<device::MockBluetoothAdapter> adapter_;
+ StrictMock<MockBluetoothConnectionFinder> connection_finder_;
+ scoped_ptr<device::MockBluetoothDevice> bluetooth_device_;
ConnectionFinder::ConnectionCallback connection_callback_;
private:
@@ -139,64 +197,45 @@ TEST_F(ProximityAuthBluetoothConnectionFinderTest, Find_NoBluetoothAdapter) {
return;
// The StrictMock will verify that no connection is created.
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
- connection_finder.Find(connection_callback_);
+ StartConnectionFinder(false);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest,
Find_BluetoothAdapterNotPresent) {
// The StrictMock will verify that no connection is created.
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
ON_CALL(*adapter_, IsPresent()).WillByDefault(Return(false));
- connection_finder.Find(connection_callback_);
+ StartConnectionFinder(false);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest,
Find_BluetoothAdapterNotPowered) {
- // The StrictMock will verify that no connection is created.
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
ON_CALL(*adapter_, IsPowered()).WillByDefault(Return(false));
- connection_finder.Find(connection_callback_);
+ // The StrictMock will verify that no connection is created.
+ StartConnectionFinder(false);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest, Find_ConnectionSucceeds) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
- MockConnection* connection = connection_finder.ExpectCreateConnection();
- connection_finder.Find(connection_callback_);
-
- connection->SetStatus(Connection::IN_PROGRESS);
-
- EXPECT_CALL(*this, OnConnectionFoundProxy(_));
- connection->SetStatus(Connection::CONNECTED);
+ MockConnection* connection = StartConnectionFinder(true);
+ SimulateDeviceConnection(connection);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest,
Find_ConnectionSucceeds_UnregistersAsObserver) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
- MockConnection* connection = connection_finder.ExpectCreateConnection();
- connection_finder.Find(connection_callback_);
+ MockConnection* connection = StartConnectionFinder(true);
+ SimulateDeviceConnection(connection);
- connection->SetStatus(Connection::IN_PROGRESS);
-
- EXPECT_CALL(*this, OnConnectionFoundProxy(_));
- EXPECT_CALL(*adapter_, RemoveObserver(&connection_finder));
- connection->SetStatus(Connection::CONNECTED);
-
- // If for some reason the connection sends more status updates, they should be
- // ignored.
+ // If for some reason the connection sends more status updates, they should
+ // be ignored.
+ base::RunLoop run_loop;
EXPECT_CALL(*this, OnConnectionFoundProxy(_)).Times(0);
connection->SetStatus(Connection::IN_PROGRESS);
connection->SetStatus(Connection::CONNECTED);
+ run_loop.RunUntilIdle();
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest,
Find_ConnectionFails_PostsTaskToPollAgain) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
- MockConnection* connection = connection_finder.ExpectCreateConnection();
- connection_finder.Find(connection_callback_);
+ MockConnection* connection = StartConnectionFinder(true);
// Simulate a connection that fails to connect.
connection->SetStatus(Connection::IN_PROGRESS);
@@ -204,40 +243,33 @@ TEST_F(ProximityAuthBluetoothConnectionFinderTest,
// A task should have been posted to poll again.
base::RunLoop run_loop;
- connection_finder.ExpectCreateConnection();
+ connection_finder_.ExpectCreateConnection();
run_loop.RunUntilIdle();
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest, Find_PollsOnAdapterPresent) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
ON_CALL(*adapter_, IsPresent()).WillByDefault(Return(false));
- EXPECT_CALL(connection_finder, CreateConnectionProxy()).Times(0);
- connection_finder.Find(connection_callback_);
+ EXPECT_CALL(connection_finder_, CreateConnectionProxy()).Times(0);
+ connection_finder_.Find(connection_callback_);
ON_CALL(*adapter_, IsPresent()).WillByDefault(Return(true));
- connection_finder.ExpectCreateConnection();
- connection_finder.AdapterPresentChanged(adapter_.get(), true);
+ connection_finder_.ExpectCreateConnection();
+ connection_finder_.AdapterPresentChanged(adapter_.get(), true);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest, Find_PollsOnAdapterPowered) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
ON_CALL(*adapter_, IsPowered()).WillByDefault(Return(false));
- EXPECT_CALL(connection_finder, CreateConnectionProxy()).Times(0);
- connection_finder.Find(connection_callback_);
+ EXPECT_CALL(connection_finder_, CreateConnectionProxy()).Times(0);
+ connection_finder_.Find(connection_callback_);
ON_CALL(*adapter_, IsPowered()).WillByDefault(Return(true));
- connection_finder.ExpectCreateConnection();
- connection_finder.AdapterPoweredChanged(adapter_.get(), true);
+ connection_finder_.ExpectCreateConnection();
+ connection_finder_.AdapterPoweredChanged(adapter_.get(), true);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest,
Find_DoesNotPollIfConnectionPending) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
- MockConnection* connection = connection_finder.ExpectCreateConnection();
- connection_finder.Find(connection_callback_);
+ MockConnection* connection = StartConnectionFinder(true);
connection->SetStatus(Connection::IN_PROGRESS);
@@ -245,16 +277,13 @@ TEST_F(ProximityAuthBluetoothConnectionFinderTest,
// that would normally trigger a new polling iteration should not do so now,
// because the delay interval between successive polling attempts has not yet
// expired.
- EXPECT_CALL(connection_finder, CreateConnectionProxy()).Times(0);
- connection_finder.AdapterPresentChanged(adapter_.get(), true);
+ EXPECT_CALL(connection_finder_, CreateConnectionProxy()).Times(0);
+ connection_finder_.AdapterPresentChanged(adapter_.get(), true);
}
TEST_F(ProximityAuthBluetoothConnectionFinderTest,
Find_ConnectionFails_PostsTaskToPollAgain_PollWaitsForTask) {
- StrictMock<MockBluetoothConnectionFinder> connection_finder;
-
- MockConnection* connection = connection_finder.ExpectCreateConnection();
- connection_finder.Find(connection_callback_);
+ MockConnection* connection = StartConnectionFinder(true);
connection->SetStatus(Connection::IN_PROGRESS);
connection->SetStatus(Connection::DISCONNECTED);
@@ -263,8 +292,8 @@ TEST_F(ProximityAuthBluetoothConnectionFinderTest,
// would normally trigger a new polling iteration should not do so now,
// because the delay interval between successive polling attempts has not yet
// expired.
- EXPECT_CALL(connection_finder, CreateConnectionProxy()).Times(0);
- connection_finder.AdapterPresentChanged(adapter_.get(), true);
+ EXPECT_CALL(connection_finder_, CreateConnectionProxy()).Times(0);
+ connection_finder_.AdapterPresentChanged(adapter_.get(), true);
// Now, allow the pending task to run, but fail early, so that no new task is
// posted.
@@ -275,8 +304,57 @@ TEST_F(ProximityAuthBluetoothConnectionFinderTest,
// Now that there is no pending task, events should once again trigger new
// polling iterations.
- connection_finder.ExpectCreateConnection();
- connection_finder.AdapterPresentChanged(adapter_.get(), true);
+ connection_finder_.ExpectCreateConnection();
+ connection_finder_.AdapterPresentChanged(adapter_.get(), true);
+}
+
+TEST_F(ProximityAuthBluetoothConnectionFinderTest,
+ Find_DeviceNotKnown_SeekDeviceSucceeds) {
+ // If the BluetoothDevice is not known by the adapter, |connection_finder|
+ // will call SeekDeviceByAddress() first to make it known.
+ ON_CALL(*adapter_, GetDevice(kBluetoothAddress))
+ .WillByDefault(Return(nullptr));
+ connection_finder_.Find(connection_callback_);
+ ASSERT_FALSE(connection_finder_.seek_callback().is_null());
+ EXPECT_FALSE(connection_finder_.seek_error_callback().is_null());
+
+ // After seeking is successful, the normal flow should resume.
+ ON_CALL(*adapter_, GetDevice(kBluetoothAddress))
+ .WillByDefault(Return(bluetooth_device_.get()));
+ MockConnection* connection = connection_finder_.ExpectCreateConnection();
+ connection_finder_.seek_callback().Run();
+ SimulateDeviceConnection(connection);
+}
+
+TEST_F(ProximityAuthBluetoothConnectionFinderTest,
+ Find_DeviceNotKnown_SeekDeviceFailThenSucceeds) {
+ // If the BluetoothDevice is not known by the adapter, |connection_finder|
+ // will call SeekDeviceByAddress() first to make it known.
+ ON_CALL(*adapter_, GetDevice(kBluetoothAddress))
+ .WillByDefault(Return(nullptr));
+ connection_finder_.Find(connection_callback_);
+ EXPECT_FALSE(connection_finder_.seek_callback().is_null());
+ ASSERT_FALSE(connection_finder_.seek_error_callback().is_null());
+
+ // If the seek fails, then |connection_finder| will post a delayed poll to
+ // reattempt the seek.
+ connection_finder_.seek_error_callback().Run("Seek failed for test.");
+ connection_finder_.ClearSeekCallbacks();
+ EXPECT_TRUE(connection_finder_.seek_callback().is_null());
+ EXPECT_TRUE(connection_finder_.seek_error_callback().is_null());
+
+ // Check that seek is reattempted.
+ base::RunLoop run_loop;
+ run_loop.RunUntilIdle();
+ ASSERT_FALSE(connection_finder_.seek_callback().is_null());
+ EXPECT_FALSE(connection_finder_.seek_error_callback().is_null());
+
+ // Successfully connect to the Bluetooth device.
+ ON_CALL(*adapter_, GetDevice(kBluetoothAddress))
+ .WillByDefault(Return(bluetooth_device_.get()));
+ MockConnection* connection = connection_finder_.ExpectCreateConnection();
+ connection_finder_.seek_callback().Run();
+ SimulateDeviceConnection(connection);
}
} // namespace proximity_auth
diff --git a/components/proximity_auth/webui/proximity_auth_webui_handler.cc b/components/proximity_auth/webui/proximity_auth_webui_handler.cc
index 4a313be..da5f19b 100644
--- a/components/proximity_auth/webui/proximity_auth_webui_handler.cc
+++ b/components/proximity_auth/webui/proximity_auth_webui_handler.cc
@@ -11,8 +11,13 @@
#include "base/prefs/pref_service.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/default_clock.h"
+#include "base/time/default_tick_clock.h"
#include "base/values.h"
-#include "components/proximity_auth/bluetooth_connection.h"
+#include "components/proximity_auth/ble/bluetooth_low_energy_connection_finder.h"
+#include "components/proximity_auth/ble/bluetooth_low_energy_device_whitelist.h"
+#include "components/proximity_auth/ble/pref_names.h"
+#include "components/proximity_auth/bluetooth_connection_finder.h"
+#include "components/proximity_auth/bluetooth_throttler_impl.h"
#include "components/proximity_auth/bluetooth_util.h"
#include "components/proximity_auth/client_impl.h"
#include "components/proximity_auth/cryptauth/base64url.h"
@@ -35,8 +40,19 @@ namespace proximity_auth {
namespace {
-// The UUID of the Smart Lock Bluetooth service.
-const char kBluetoothServiceUUID[] = "704EE561-3782-405A-A14B-2D47A2DDCDDF";
+// The UUID of the Smart Lock classic Bluetooth service.
+const char kClassicBluetoothServiceUUID[] =
+ "704EE561-3782-405A-A14B-2D47A2DDCDDF";
+
+// The UUID of the Bluetooth Low Energy service.
+const char kBLESmartLockServiceUUID[] = "b3b7e28e-a000-3e17-bd86-6e97b9e28c11";
+
+// The UUID of the characteristic used to send data to the peripheral.
+const char kBLEToPeripheralCharUUID[] = "977c6674-1239-4e72-993b-502369b8bb5a";
+
+// The UUID of the characteristic used to receive data from the peripheral.
+const char kBLEFromPeripheralCharUUID[] =
+ "f4b904a2-a030-43b3-98a8-221c536c03cb";
// Keys in the JSON representation of a log message.
const char kLogMessageTextKey[] = "text";
@@ -105,7 +121,6 @@ scoped_ptr<base::DictionaryValue> CreateSyncStateDictionary(
ProximityAuthWebUIHandler::ProximityAuthWebUIHandler(
ProximityAuthUIDelegate* delegate)
: delegate_(delegate),
- web_contents_initialized_(false),
weak_ptr_factory_(this) {
cryptauth_client_factory_ = delegate_->CreateCryptAuthClientFactory();
}
@@ -313,7 +328,7 @@ void ProximityAuthWebUIHandler::ToggleConnection(const base::ListValue* args) {
if (unlock_key.public_key() == public_key) {
// Check if there is an existing connection to disconnect from first.
if (connection && connection->IsConnected() &&
- connection->remote_device().public_key == public_key) {
+ selected_remote_device_.public_key == public_key) {
PA_LOG(INFO) << "Disconnecting from "
<< unlock_key.friendly_device_name() << "["
<< unlock_key.bluetooth_address() << "]";
@@ -322,8 +337,8 @@ void ProximityAuthWebUIHandler::ToggleConnection(const base::ListValue* args) {
}
// Derive the PSK before connecting to the device.
- PA_LOG(INFO) << "Connecting to " << unlock_key.friendly_device_name()
- << "[" << unlock_key.bluetooth_address() << "]";
+ PA_LOG(INFO) << "Deriving PSK before connecting to "
+ << unlock_key.friendly_device_name();
secure_message_delegate_ = delegate_->CreateSecureMessageDelegate();
secure_message_delegate_->DeriveKey(
user_private_key_, unlock_key.public_key(),
@@ -509,7 +524,7 @@ scoped_ptr<base::ListValue> ProximityAuthWebUIHandler::GetUnlockKeysList() {
}
Connection* ProximityAuthWebUIHandler::GetConnection() {
- Connection* connection = bluetooth_connection_.get();
+ Connection* connection = connection_.get();
if (client_) {
DCHECK(!connection);
connection = client_->connection();
@@ -525,38 +540,65 @@ void ProximityAuthWebUIHandler::OnPSKDerived(
return;
}
- RemoteDevice remote_device(
- unlock_key.friendly_device_name(), unlock_key.public_key(),
- unlock_key.bluetooth_address(), persistent_symmetric_key);
-
- bluetooth_connection_.reset(new BluetoothConnection(
- remote_device, device::BluetoothUUID(kBluetoothServiceUUID)));
- bluetooth_connection_->AddObserver(this);
+ selected_remote_device_ =
+ RemoteDevice(unlock_key.friendly_device_name(), unlock_key.public_key(),
+ unlock_key.bluetooth_address(), persistent_symmetric_key);
+
+ // TODO(tengs): We distinguish whether the unlock key uses classic Bluetooth
+ // or BLE based on the presence of the |bluetooth_address| field. However, we
+ // should ideally have a separate field specifying the protocol.
+ if (selected_remote_device_.bluetooth_address.empty())
+ FindBluetoothLowEnergyConnection(selected_remote_device_);
+ else
+ FindBluetoothClassicConnection(selected_remote_device_);
+}
+
+void ProximityAuthWebUIHandler::FindBluetoothClassicConnection(
+ const RemoteDevice& remote_device) {
+ PA_LOG(INFO) << "Finding classic Bluetooth device " << remote_device.name
+ << " [" << remote_device.bluetooth_address << "].";
+
+ // TODO(tengs): Set a timeout to stop the connection finder eventually.
+ connection_finder_.reset(new BluetoothConnectionFinder(
+ remote_device, device::BluetoothUUID(kClassicBluetoothServiceUUID),
+ base::TimeDelta::FromSeconds(3)));
+ connection_finder_->Find(
+ base::Bind(&ProximityAuthWebUIHandler::OnConnectionFound,
+ weak_ptr_factory_.GetWeakPtr()));
- // This SeekDeviceByAddress operation is needed to connect to a device if
- // it is not already known to the local device.
- bluetooth_util::SeekDeviceByAddress(
- remote_device.bluetooth_address,
- base::Bind(&ProximityAuthWebUIHandler::OnSeekedDeviceByAddress,
- weak_ptr_factory_.GetWeakPtr()),
- base::Bind(&ProximityAuthWebUIHandler::OnSeekedDeviceByAddressError,
- weak_ptr_factory_.GetWeakPtr()),
- content::BrowserThread::GetBlockingPool()
- ->GetTaskRunnerWithShutdownBehavior(
- base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN)
- .get());
+ web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
+ *GetUnlockKeysList());
}
-void ProximityAuthWebUIHandler::OnSeekedDeviceByAddress() {
- PA_LOG(INFO) << "Found Bluetooth device: "
- << bluetooth_connection_->remote_device().bluetooth_address;
- bluetooth_connection_->Connect();
-}
+void ProximityAuthWebUIHandler::FindBluetoothLowEnergyConnection(
+ const RemoteDevice& remote_device) {
+ PrefService* pref_service = delegate_->GetPrefService();
+ if (!pref_service->FindPreference(
+ prefs::kBluetoothLowEnergyDeviceWhitelist)) {
+ PA_LOG(ERROR) << "Please enable the BLE experiment in chrome://flags.";
+ return;
+ }
-void ProximityAuthWebUIHandler::OnSeekedDeviceByAddressError(
- const std::string& error_message) {
- PA_LOG(WARNING) << "Failed to seek device by address: "
- << bluetooth_connection_->remote_device().bluetooth_address;
+ PA_LOG(INFO) << "Finding Bluetooth Low Energy device " << remote_device.name;
+ if (!bluetooth_throttler_) {
+ bluetooth_throttler_.reset(new BluetoothThrottlerImpl(
+ make_scoped_ptr(new base::DefaultTickClock())));
+ }
+
+ ble_device_whitelist_.reset(
+ new BluetoothLowEnergyDeviceWhitelist(delegate_->GetPrefService()));
+
+ // TODO(tengs): Set a timeout to stop the connection finder eventually.
+ connection_finder_.reset(new BluetoothLowEnergyConnectionFinder(
+ kBLESmartLockServiceUUID, kBLEToPeripheralCharUUID,
+ kBLEFromPeripheralCharUUID, ble_device_whitelist_.get(),
+ bluetooth_throttler_.get(), 3));
+ connection_finder_->Find(
+ base::Bind(&ProximityAuthWebUIHandler::OnConnectionFound,
+ weak_ptr_factory_.GetWeakPtr()));
+
+ web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
+ *GetUnlockKeysList());
}
void ProximityAuthWebUIHandler::OnAuthenticationResult(
@@ -565,7 +607,7 @@ void ProximityAuthWebUIHandler::OnAuthenticationResult(
secure_context_ = secure_context.Pass();
// Create the ClientImpl asynchronously. |client_| registers itself as an
- // observer of |bluetooth_connection_|, so creating it synchronously would
+ // observer of |connection_|, so creating it synchronously would
// trigger |OnSendComplete()| as an observer call for |client_|.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
@@ -573,9 +615,29 @@ void ProximityAuthWebUIHandler::OnAuthenticationResult(
weak_ptr_factory_.GetWeakPtr()));
}
+void ProximityAuthWebUIHandler::OnConnectionFound(
+ scoped_ptr<Connection> connection) {
+ DCHECK(connection->IsConnected());
+ connection_ = connection.Pass();
+ connection_->AddObserver(this);
+
+ web_ui()->CallJavascriptFunction("LocalStateInterface.onUnlockKeysChanged",
+ *GetUnlockKeysList());
+
+ // TODO(tengs): Create an authenticator for BLE connections.
+ if (selected_remote_device_.bluetooth_address.empty())
+ return;
+
+ authenticator_.reset(new DeviceToDeviceAuthenticator(
+ connection_.get(), delegate_->GetAccountId(),
+ delegate_->CreateSecureMessageDelegate()));
+ authenticator_->Authenticate(
+ base::Bind(&ProximityAuthWebUIHandler::OnAuthenticationResult,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
void ProximityAuthWebUIHandler::CreateStatusUpdateClient() {
- client_.reset(
- new ClientImpl(bluetooth_connection_.Pass(), secure_context_.Pass()));
+ client_.reset(new ClientImpl(connection_.Pass(), secure_context_.Pass()));
client_->AddObserver(this);
}
@@ -610,19 +672,16 @@ ProximityAuthWebUIHandler::ExternalDeviceInfoToDictionary(
return unlock_key.public_key() == public_key;
});
- if (iterator == device_manager_->unlock_keys().end())
+ if (iterator == device_manager_->unlock_keys().end() ||
+ selected_remote_device_.public_key != device_info.public_key())
return dictionary;
// Fill in the current Bluetooth connection status.
- Connection* connection = GetConnection();
- if (!connection ||
- connection->remote_device().public_key != device_info.public_key())
- return dictionary;
-
std::string connection_status = kExternalDeviceDisconnected;
- if (connection->IsConnected()) {
+ Connection* connection = GetConnection();
+ if (connection && connection->IsConnected()) {
connection_status = kExternalDeviceConnected;
- } else if (connection->status() == Connection::IN_PROGRESS) {
+ } else if (connection_finder_) {
connection_status = kExternalDeviceConnecting;
}
dictionary->SetString(kExternalDeviceConnectionStatus, connection_status);
@@ -665,15 +724,11 @@ void ProximityAuthWebUIHandler::OnConnectionStatusChanged(
Connection::Status new_status) {
PA_LOG(INFO) << "Connection status changed from " << old_status << " to "
<< new_status;
- if (new_status == Connection::CONNECTED) {
- authenticator_.reset(new DeviceToDeviceAuthenticator(
- connection, delegate_->GetAccountId(),
- delegate_->CreateSecureMessageDelegate()));
- authenticator_->Authenticate(
- base::Bind(&ProximityAuthWebUIHandler::OnAuthenticationResult,
- weak_ptr_factory_.GetWeakPtr()));
- } else if (new_status == Connection::DISCONNECTED) {
+
+ if (new_status == Connection::DISCONNECTED) {
last_remote_status_update_.reset();
+ selected_remote_device_ = RemoteDevice();
+ connection_finder_.reset();
}
scoped_ptr<base::ListValue> unlock_keys = GetUnlockKeysList();
diff --git a/components/proximity_auth/webui/proximity_auth_webui_handler.h b/components/proximity_auth/webui/proximity_auth_webui_handler.h
index 4de4abf..1f87687 100644
--- a/components/proximity_auth/webui/proximity_auth_webui_handler.h
+++ b/components/proximity_auth/webui/proximity_auth_webui_handler.h
@@ -30,7 +30,10 @@ namespace proximity_auth {
class Authenticator;
class BluetoothConnection;
+class BluetoothThrottler;
+class BluetoothLowEnergyDeviceWhitelist;
class Connection;
+class ConnectionFinder;
class ClientImpl;
class ReachablePhoneFlow;
struct RemoteStatusUpdate;
@@ -100,9 +103,14 @@ class ProximityAuthWebUIHandler : public content::WebUIMessageHandler,
void OnPSKDerived(const cryptauth::ExternalDeviceInfo& unlock_key,
const std::string& persistent_symmetric_key);
- // Callbacks for bluetooth_util::SeekDeviceByAddress().
- void OnSeekedDeviceByAddress();
- void OnSeekedDeviceByAddressError(const std::string& error_message);
+ // Tries to create a classic Bluetooth connection to the unlock key.
+ void FindBluetoothClassicConnection(const RemoteDevice& remote_device);
+
+ // Tries to create a Bluetooth Low Energy connection to the unlock key.
+ void FindBluetoothLowEnergyConnection(const RemoteDevice& remote_device);
+
+ // Called when |connection_finder_| finds a connection.
+ void OnConnectionFound(scoped_ptr<Connection> connection);
// Callback when |authenticator_| completes authentication.
void OnAuthenticationResult(Authenticator::Result result,
@@ -155,9 +163,6 @@ class ProximityAuthWebUIHandler : public content::WebUIMessageHandler,
// The flow for getting a list of reachable phones.
scoped_ptr<ReachablePhoneFlow> reachable_phone_flow_;
- // True if the WebContents backing the WebUI has been initialized.
- bool web_contents_initialized_;
-
// Member variables related to CryptAuth debugging.
// TODO(tengs): These members are temporarily used for development.
scoped_ptr<PrefService> pref_service;
@@ -170,7 +175,11 @@ class ProximityAuthWebUIHandler : public content::WebUIMessageHandler,
// Member variables for connecting to and authenticating the remote device.
// TODO(tengs): Support multiple simultaenous connections.
scoped_ptr<SecureMessageDelegate> secure_message_delegate_;
- scoped_ptr<BluetoothConnection> bluetooth_connection_;
+ scoped_ptr<BluetoothLowEnergyDeviceWhitelist> ble_device_whitelist_;
+ RemoteDevice selected_remote_device_;
+ scoped_ptr<BluetoothThrottler> bluetooth_throttler_;
+ scoped_ptr<ConnectionFinder> connection_finder_;
+ scoped_ptr<Connection> connection_;
scoped_ptr<Authenticator> authenticator_;
scoped_ptr<SecureContext> secure_context_;
scoped_ptr<ClientImpl> client_;