diff options
author | tengs <tengs@chromium.org> | 2015-08-10 17:21:01 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-08-11 00:21:37 +0000 |
commit | d740523e5f417fb38ee37b9dcb322e79cd83e736 (patch) | |
tree | 14dc1b2c232232660c2f5619aa82598d8f846b19 /components/proximity_auth | |
parent | bde397b5f4db6102eba9540f77c61d3942556a36 (diff) | |
download | chromium_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')
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_; |