diff --git a/chromeos/dbus/fake_bluetooth_device_client.cc b/chromeos/dbus/fake_bluetooth_device_client.cc index 2dd5aaea70e6..87b6591d9315 100644 --- a/chromeos/dbus/fake_bluetooth_device_client.cc +++ b/chromeos/dbus/fake_bluetooth_device_client.cc @@ -293,7 +293,8 @@ void FakeBluetoothDeviceClient::Connect( } if (properties->paired.value() != true && - object_path != dbus::ObjectPath(kConnectUnpairablePath)) { + object_path != dbus::ObjectPath(kConnectUnpairablePath) && + object_path != dbus::ObjectPath(kLowEnergyPath)) { // Must be paired. error_callback.Run(bluetooth_device::kErrorFailed, "Not paired"); return; diff --git a/device/bluetooth/bluetooth.gyp b/device/bluetooth/bluetooth.gyp index b4df6052104b..e0f11347a7fd 100644 --- a/device/bluetooth/bluetooth.gyp +++ b/device/bluetooth/bluetooth.gyp @@ -49,6 +49,8 @@ 'bluetooth_gatt_characteristic.h', 'bluetooth_gatt_connection.cc', 'bluetooth_gatt_connection.h', + 'bluetooth_gatt_connection_chromeos.cc', + 'bluetooth_gatt_connection_chromeos.h', 'bluetooth_gatt_descriptor.cc', 'bluetooth_gatt_descriptor.h', 'bluetooth_gatt_service.cc', @@ -135,6 +137,8 @@ 'test/mock_bluetooth_discovery_session.h', 'test/mock_bluetooth_gatt_characteristic.cc', 'test/mock_bluetooth_gatt_characteristic.h', + 'test/mock_bluetooth_gatt_connection.cc', + 'test/mock_bluetooth_gatt_connection.h', 'test/mock_bluetooth_gatt_descriptor.cc', 'test/mock_bluetooth_gatt_descriptor.h', 'test/mock_bluetooth_gatt_service.cc', diff --git a/device/bluetooth/bluetooth_device_chromeos.cc b/device/bluetooth/bluetooth_device_chromeos.cc index e0fc0fde0824..c8085699ef6b 100644 --- a/device/bluetooth/bluetooth_device_chromeos.cc +++ b/device/bluetooth/bluetooth_device_chromeos.cc @@ -18,6 +18,7 @@ #include "chromeos/dbus/dbus_thread_manager.h" #include "dbus/bus.h" #include "device/bluetooth/bluetooth_adapter_chromeos.h" +#include "device/bluetooth/bluetooth_gatt_connection_chromeos.h" #include "device/bluetooth/bluetooth_pairing_chromeos.h" #include "device/bluetooth/bluetooth_remote_gatt_service_chromeos.h" #include "device/bluetooth/bluetooth_socket.h" @@ -453,8 +454,13 @@ void BluetoothDeviceChromeOS::ConnectToService( void BluetoothDeviceChromeOS::CreateGattConnection( const GattConnectionCallback& callback, const ConnectErrorCallback& error_callback) { - // TODO(armansito): Implement. - error_callback.Run(ERROR_UNSUPPORTED_DEVICE); + // TODO(armansito): Until there is a way to create a reference counted GATT + // connection in bluetoothd, simply do a regular connect. + Connect(NULL, + base::Bind(&BluetoothDeviceChromeOS::OnCreateGattConnection, + weak_ptr_factory_.GetWeakPtr(), + callback), + error_callback); } void BluetoothDeviceChromeOS::StartConnectionMonitor( @@ -568,6 +574,14 @@ void BluetoothDeviceChromeOS::OnConnect(bool after_pairing, callback.Run(); } +void BluetoothDeviceChromeOS::OnCreateGattConnection( + const GattConnectionCallback& callback) { + scoped_ptr conn( + new BluetoothGattConnectionChromeOS( + adapter_, GetAddress(), object_path_)); + callback.Run(conn.Pass()); +} + void BluetoothDeviceChromeOS::OnConnectError( bool after_pairing, const ConnectErrorCallback& error_callback, diff --git a/device/bluetooth/bluetooth_device_chromeos.h b/device/bluetooth/bluetooth_device_chromeos.h index 21f27c317ba8..b7247f5571a2 100644 --- a/device/bluetooth/bluetooth_device_chromeos.h +++ b/device/bluetooth/bluetooth_device_chromeos.h @@ -119,6 +119,7 @@ class BluetoothDeviceChromeOS const ConnectErrorCallback& error_callback); void OnConnect(bool after_pairing, const base::Closure& callback); + void OnCreateGattConnection(const GattConnectionCallback& callback); void OnConnectError(bool after_pairing, const ConnectErrorCallback& error_callback, const std::string& error_name, diff --git a/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc b/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc index 235cc72110c3..0454399e117b 100644 --- a/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc +++ b/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc @@ -17,6 +17,7 @@ #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_gatt_connection.h" #include "device/bluetooth/bluetooth_gatt_descriptor.h" #include "device/bluetooth/bluetooth_gatt_service.h" #include "device/bluetooth/bluetooth_uuid.h" @@ -25,6 +26,7 @@ using device::BluetoothAdapter; using device::BluetoothDevice; using device::BluetoothGattCharacteristic; +using device::BluetoothGattConnection; using device::BluetoothGattDescriptor; using device::BluetoothGattService; using device::BluetoothUUID; @@ -341,6 +343,7 @@ class BluetoothGattChromeOSTest : public testing::Test { virtual void TearDown() { adapter_ = NULL; + gatt_conn_.reset(); DBusThreadManager::Shutdown(); } @@ -366,10 +369,19 @@ class BluetoothGattChromeOSTest : public testing::Test { last_read_value_ = value; } + void GattConnectionCallback(scoped_ptr conn) { + ++success_callback_count_; + gatt_conn_ = conn.Pass(); + } + void ErrorCallback() { ++error_callback_count_; } + void ConnectErrorCallback(BluetoothDevice::ConnectErrorCode error) { + ++error_callback_count_; + } + protected: base::MessageLoop message_loop_; @@ -378,6 +390,7 @@ class BluetoothGattChromeOSTest : public testing::Test { FakeBluetoothGattCharacteristicClient* fake_bluetooth_gatt_characteristic_client_; FakeBluetoothGattDescriptorClient* fake_bluetooth_gatt_descriptor_client_; + scoped_ptr gatt_conn_; scoped_refptr adapter_; int success_callback_count_; @@ -385,6 +398,81 @@ class BluetoothGattChromeOSTest : public testing::Test { std::vector last_read_value_; }; +TEST_F(BluetoothGattChromeOSTest, GattConnection) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + ASSERT_FALSE(device->IsConnected()); + ASSERT_FALSE(gatt_conn_.get()); + ASSERT_EQ(0, success_callback_count_); + ASSERT_EQ(0, error_callback_count_); + + device->CreateGattConnection( + base::Bind(&BluetoothGattChromeOSTest::GattConnectionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_TRUE(gatt_conn_->IsConnected()); + EXPECT_EQ(FakeBluetoothDeviceClient::kLowEnergyAddress, + gatt_conn_->GetDeviceAddress()); + + gatt_conn_->Disconnect( + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + EXPECT_FALSE(gatt_conn_->IsConnected()); + + device->CreateGattConnection( + base::Bind(&BluetoothGattChromeOSTest::GattConnectionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_TRUE(gatt_conn_->IsConnected()); + + device->Disconnect( + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(4, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_FALSE(gatt_conn_->IsConnected()); + + device->CreateGattConnection( + base::Bind(&BluetoothGattChromeOSTest::GattConnectionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ConnectErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(5, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(device->IsConnected()); + EXPECT_TRUE(gatt_conn_->IsConnected()); + + fake_bluetooth_device_client_->RemoveDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_TRUE(gatt_conn_.get()); + EXPECT_FALSE(gatt_conn_->IsConnected()); +} + TEST_F(BluetoothGattChromeOSTest, GattServiceAddedAndRemoved) { // Create a fake LE device. We store the device pointer here because this is a // test. It's unsafe to do this in production as the device might get deleted. diff --git a/device/bluetooth/bluetooth_gatt_connection_chromeos.cc b/device/bluetooth/bluetooth_gatt_connection_chromeos.cc new file mode 100644 index 000000000000..94b7555170b8 --- /dev/null +++ b/device/bluetooth/bluetooth_gatt_connection_chromeos.cc @@ -0,0 +1,107 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/bluetooth/bluetooth_gatt_connection_chromeos.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" + +namespace chromeos { + +BluetoothGattConnectionChromeOS::BluetoothGattConnectionChromeOS( + scoped_refptr adapter, + const std::string& device_address, + const dbus::ObjectPath& object_path) + : connected_(true), + adapter_(adapter), + device_address_(device_address), + object_path_(object_path) { + DCHECK(adapter_.get()); + DCHECK(!device_address_.empty()); + DCHECK(object_path_.IsValid()); + + DBusThreadManager::Get()->GetBluetoothDeviceClient()->AddObserver(this); +} + +BluetoothGattConnectionChromeOS::~BluetoothGattConnectionChromeOS() { + DBusThreadManager::Get()->GetBluetoothDeviceClient()->RemoveObserver(this); + Disconnect(base::Bind(&base::DoNothing)); +} + +std::string BluetoothGattConnectionChromeOS::GetDeviceAddress() const { + return device_address_; +} + +bool BluetoothGattConnectionChromeOS::IsConnected() { + // Lazily determine the activity state of the connection. If already + // marked as inactive, then return false. Otherwise, explicitly mark + // |connected_| as false if the device is removed or disconnected. We do this, + // so that if this method is called during a call to DeviceRemoved or + // DeviceChanged somewhere else, it returns the correct status. + if (!connected_) + return false; + + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()-> + GetProperties(object_path_); + if (!properties || !properties->connected.value()) + connected_ = false; + + return connected_; +} + +void BluetoothGattConnectionChromeOS::Disconnect( + const base::Closure& callback) { + if (!connected_) { + VLOG(1) << "Connection already inactive."; + callback.Run(); + return; + } + + // TODO(armansito): There isn't currently a good way to manage the ownership + // of a connection between Chrome and bluetoothd plugins/profiles. Until + // a proper reference count is kept by bluetoothd, we might unwittingly kill + // a connection that is managed by the daemon (e.g. HoG). For now, just return + // success to indicate that this BluetoothGattConnection is no longer active, + // even though the underlying connection won't actually be disconnected. This + // technically doesn't violate the contract put forth by this API. + connected_ = false; + callback.Run(); +} + +void BluetoothGattConnectionChromeOS::DeviceRemoved( + const dbus::ObjectPath& object_path) { + if (object_path != object_path_) + return; + + connected_ = false; +} + +void BluetoothGattConnectionChromeOS::DevicePropertyChanged( + const dbus::ObjectPath& object_path, + const std::string& property_name) { + if (object_path != object_path_) + return; + + if (!connected_) + return; + + BluetoothDeviceClient::Properties* properties = + DBusThreadManager::Get()->GetBluetoothDeviceClient()-> + GetProperties(object_path_); + + if (!properties) { + connected_ = false; + return; + } + + if (property_name == properties->connected.name() && + !properties->connected.value()) + connected_ = false; +} + +} // namespace chromeos diff --git a/device/bluetooth/bluetooth_gatt_connection_chromeos.h b/device/bluetooth/bluetooth_gatt_connection_chromeos.h new file mode 100644 index 000000000000..beade19d17bf --- /dev/null +++ b/device/bluetooth/bluetooth_gatt_connection_chromeos.h @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_CHROMEOS_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_CHROMEOS_H_ + +#include + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "chromeos/dbus/bluetooth_device_client.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_gatt_connection.h" + +namespace device { + +class BluetoothAdapter; + +} // namespace device + +namespace chromeos { + +// BluetoothGattConnectionChromeOS implements BluetoothGattConnection for the +// Chrome OS platform. +class BluetoothGattConnectionChromeOS + : public device::BluetoothGattConnection, + public BluetoothDeviceClient::Observer { + public: + explicit BluetoothGattConnectionChromeOS( + scoped_refptr adapter, + const std::string& device_address, + const dbus::ObjectPath& object_path); + virtual ~BluetoothGattConnectionChromeOS(); + + // BluetoothGattConnection overrides. + virtual std::string GetDeviceAddress() const OVERRIDE; + virtual bool IsConnected() OVERRIDE; + virtual void Disconnect(const base::Closure& callback) OVERRIDE; + + private: + + // chromeos::BluetoothDeviceClient::Observer overrides. + virtual void DeviceRemoved(const dbus::ObjectPath& object_path) OVERRIDE; + virtual void DevicePropertyChanged(const dbus::ObjectPath& object_path, + const std::string& property_name) OVERRIDE; + + // True, if the connection is currently active. + bool connected_; + + // The Bluetooth adapter that this connection is associated with. + scoped_refptr adapter_; + + // Bluetooth address of the underlying device. + std::string device_address_; + + // D-Bus object path of the underlying device. This is used to filter observer + // events. + dbus::ObjectPath object_path_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothGattConnectionChromeOS); +}; + +} // namespace chromeos + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_GATT_CONNECTION_CHROMEOS_H_ diff --git a/device/bluetooth/test/mock_bluetooth_gatt_connection.cc b/device/bluetooth/test/mock_bluetooth_gatt_connection.cc new file mode 100644 index 000000000000..7f06a9f1bf70 --- /dev/null +++ b/device/bluetooth/test/mock_bluetooth_gatt_connection.cc @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h" + +using ::testing::Return; + +namespace device { + +MockBluetoothGattConnection::MockBluetoothGattConnection( + const std::string& device_address) { + ON_CALL(*this, GetDeviceAddress()).WillByDefault(Return(device_address)); + ON_CALL(*this, IsConnected()).WillByDefault(Return(true)); +} + +MockBluetoothGattConnection::~MockBluetoothGattConnection() {} + +} // namespace device diff --git a/device/bluetooth/test/mock_bluetooth_gatt_connection.h b/device/bluetooth/test/mock_bluetooth_gatt_connection.h new file mode 100644 index 000000000000..268ebf92af9a --- /dev/null +++ b/device/bluetooth/test/mock_bluetooth_gatt_connection.h @@ -0,0 +1,25 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_GATT_CONNECTION_H_ +#define DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_GATT_CONNECTION_H_ + +#include "device/bluetooth/bluetooth_gatt_connection.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace device { + +class MockBluetoothGattConnection : public BluetoothGattConnection { + public: + MockBluetoothGattConnection(const std::string& device_address); + virtual ~MockBluetoothGattConnection(); + + MOCK_CONST_METHOD0(GetDeviceAddress, std::string()); + MOCK_METHOD0(IsConnected, bool()); + MOCK_METHOD1(Disconnect, void(const base::Closure&)); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_TEST_MOCK_BLUETOOTH_GATT_CONNECTION_H_