diff options
Diffstat (limited to 'chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc')
-rw-r--r-- | chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc | 1346 |
1 files changed, 1346 insertions, 0 deletions
diff --git a/chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc b/chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc new file mode 100644 index 00000000000..673ed5f35fb --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_gatt_chromeos_unittest.cc @@ -0,0 +1,1346 @@ +// 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 "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "chromeos/dbus/fake_bluetooth_adapter_client.h" +#include "chromeos/dbus/fake_bluetooth_agent_manager_client.h" +#include "chromeos/dbus/fake_bluetooth_device_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_characteristic_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_descriptor_client.h" +#include "chromeos/dbus/fake_bluetooth_gatt_service_client.h" +#include "chromeos/dbus/fake_bluetooth_input_client.h" +#include "chromeos/dbus/fake_dbus_thread_manager.h" +#include "dbus/object_path.h" +#include "device/bluetooth/bluetooth_adapter.h" +#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_notify_session.h" +#include "device/bluetooth/bluetooth_gatt_service.h" +#include "device/bluetooth/bluetooth_uuid.h" +#include "testing/gtest/include/gtest/gtest.h" + +using device::BluetoothAdapter; +using device::BluetoothDevice; +using device::BluetoothGattCharacteristic; +using device::BluetoothGattConnection; +using device::BluetoothGattDescriptor; +using device::BluetoothGattService; +using device::BluetoothGattNotifySession; +using device::BluetoothUUID; + +namespace chromeos { + +namespace { + +const BluetoothUUID kHeartRateMeasurementUUID( + FakeBluetoothGattCharacteristicClient::kHeartRateMeasurementUUID); +const BluetoothUUID kBodySensorLocationUUID( + FakeBluetoothGattCharacteristicClient::kBodySensorLocationUUID); +const BluetoothUUID kHeartRateControlPointUUID( + FakeBluetoothGattCharacteristicClient::kHeartRateControlPointUUID); + +// Compares GATT characteristic/descriptor values. Returns true, if the values +// are equal. +bool ValuesEqual(const std::vector<uint8>& value0, + const std::vector<uint8>& value1) { + if (value0.size() != value1.size()) + return false; + for (size_t i = 0; i < value0.size(); ++i) + if (value0[i] != value1[i]) + return false; + return true; +} + +class TestDeviceObserver : public BluetoothDevice::Observer { + public: + TestDeviceObserver(scoped_refptr<BluetoothAdapter> adapter, + BluetoothDevice* device) + : gatt_service_added_count_(0), + gatt_service_removed_count_(0), + device_address_(device->GetAddress()), + adapter_(adapter) { + device->AddObserver(this); + } + + virtual ~TestDeviceObserver() { + BluetoothDevice* device = adapter_->GetDevice(device_address_); + if (device) + device->RemoveObserver(this); + } + + // BluetoothDevice::Observer overrides. + virtual void GattServiceAdded( + BluetoothDevice* device, + BluetoothGattService* service) OVERRIDE { + ASSERT_EQ(device_address_, device->GetAddress()); + + ++gatt_service_added_count_; + last_gatt_service_id_ = service->GetIdentifier(); + last_gatt_service_uuid_ = service->GetUUID(); + + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + + EXPECT_EQ(device->GetGattService(last_gatt_service_id_), service); + + QuitMessageLoop(); + } + + virtual void GattServiceRemoved( + BluetoothDevice* device, + BluetoothGattService* service) OVERRIDE { + ASSERT_EQ(device_address_, device->GetAddress()); + + ++gatt_service_removed_count_; + last_gatt_service_id_ = service->GetIdentifier(); + last_gatt_service_uuid_ = service->GetUUID(); + + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + + // The device should return NULL for this service. + EXPECT_FALSE(device->GetGattService(last_gatt_service_id_)); + + QuitMessageLoop(); + } + + int gatt_service_added_count_; + int gatt_service_removed_count_; + std::string last_gatt_service_id_; + BluetoothUUID last_gatt_service_uuid_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + + std::string device_address_; + scoped_refptr<BluetoothAdapter> adapter_; +}; + +class TestGattServiceObserver : public BluetoothGattService::Observer { + public: + TestGattServiceObserver(scoped_refptr<BluetoothAdapter> adapter, + BluetoothDevice* device, + BluetoothGattService* service) + : gatt_service_changed_count_(0), + gatt_characteristic_added_count_(0), + gatt_characteristic_removed_count_(0), + gatt_characteristic_value_changed_count_(0), + gatt_descriptor_added_count_(0), + gatt_descriptor_removed_count_(0), + gatt_descriptor_value_changed_count_(0), + device_address_(device->GetAddress()), + gatt_service_id_(service->GetIdentifier()), + adapter_(adapter) { + service->AddObserver(this); + } + + virtual ~TestGattServiceObserver() { + // See if either the device or the service even exist. + BluetoothDevice* device = adapter_->GetDevice(device_address_); + if (!device) + return; + + BluetoothGattService* service = device->GetGattService(gatt_service_id_); + if (!service) + return; + + service->RemoveObserver(this); + } + + // BluetoothGattService::Observer overrides. + virtual void GattServiceChanged(BluetoothGattService* service) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + ++gatt_service_changed_count_; + + QuitMessageLoop(); + } + + virtual void GattCharacteristicAdded( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + + ++gatt_characteristic_added_count_; + last_gatt_characteristic_id_ = characteristic->GetIdentifier(); + last_gatt_characteristic_uuid_ = characteristic->GetUUID(); + + EXPECT_EQ(service->GetCharacteristic(last_gatt_characteristic_id_), + characteristic); + EXPECT_EQ(service, characteristic->GetService()); + + QuitMessageLoop(); + } + + virtual void GattCharacteristicRemoved( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + + ++gatt_characteristic_removed_count_; + last_gatt_characteristic_id_ = characteristic->GetIdentifier(); + last_gatt_characteristic_uuid_ = characteristic->GetUUID(); + + // The service should return NULL for this characteristic. + EXPECT_FALSE(service->GetCharacteristic(last_gatt_characteristic_id_)); + EXPECT_EQ(service, characteristic->GetService()); + + QuitMessageLoop(); + } + + virtual void GattCharacteristicValueChanged( + BluetoothGattService* service, + BluetoothGattCharacteristic* characteristic, + const std::vector<uint8>& value) OVERRIDE { + ASSERT_EQ(gatt_service_id_, service->GetIdentifier()); + + ++gatt_characteristic_value_changed_count_; + last_gatt_characteristic_id_ = characteristic->GetIdentifier(); + last_gatt_characteristic_uuid_ = characteristic->GetUUID(); + last_changed_characteristic_value_ = value; + + EXPECT_EQ(service->GetCharacteristic(last_gatt_characteristic_id_), + characteristic); + EXPECT_EQ(service, characteristic->GetService()); + + QuitMessageLoop(); + } + + virtual void GattDescriptorAdded( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor) OVERRIDE { + ASSERT_EQ(gatt_service_id_, characteristic->GetService()->GetIdentifier()); + + ++gatt_descriptor_added_count_; + last_gatt_descriptor_id_ = descriptor->GetIdentifier(); + last_gatt_descriptor_uuid_ = descriptor->GetUUID(); + + EXPECT_EQ(characteristic->GetDescriptor(last_gatt_descriptor_id_), + descriptor); + EXPECT_EQ(characteristic, descriptor->GetCharacteristic()); + + QuitMessageLoop(); + } + + virtual void GattDescriptorRemoved( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor) OVERRIDE { + ASSERT_EQ(gatt_service_id_, characteristic->GetService()->GetIdentifier()); + + ++gatt_descriptor_removed_count_; + last_gatt_descriptor_id_ = descriptor->GetIdentifier(); + last_gatt_descriptor_uuid_ = descriptor->GetUUID(); + + // The characteristic should return NULL for this descriptor.. + EXPECT_FALSE(characteristic->GetDescriptor(last_gatt_descriptor_id_)); + EXPECT_EQ(characteristic, descriptor->GetCharacteristic()); + + QuitMessageLoop(); + } + + virtual void GattDescriptorValueChanged( + BluetoothGattCharacteristic* characteristic, + BluetoothGattDescriptor* descriptor, + const std::vector<uint8>& value) OVERRIDE { + ASSERT_EQ(gatt_service_id_, characteristic->GetService()->GetIdentifier()); + + ++gatt_descriptor_value_changed_count_; + last_gatt_descriptor_id_ = descriptor->GetIdentifier(); + last_gatt_descriptor_uuid_ = descriptor->GetUUID(); + last_changed_descriptor_value_ = value; + + EXPECT_EQ(characteristic->GetDescriptor(last_gatt_descriptor_id_), + descriptor); + EXPECT_EQ(characteristic, descriptor->GetCharacteristic()); + + QuitMessageLoop(); + } + + int gatt_service_changed_count_; + int gatt_characteristic_added_count_; + int gatt_characteristic_removed_count_; + int gatt_characteristic_value_changed_count_; + int gatt_descriptor_added_count_; + int gatt_descriptor_removed_count_; + int gatt_descriptor_value_changed_count_; + std::string last_gatt_characteristic_id_; + BluetoothUUID last_gatt_characteristic_uuid_; + std::vector<uint8> last_changed_characteristic_value_; + std::string last_gatt_descriptor_id_; + BluetoothUUID last_gatt_descriptor_uuid_; + std::vector<uint8> last_changed_descriptor_value_; + + private: + // Some tests use a message loop since background processing is simulated; + // break out of those loops. + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + + std::string device_address_; + std::string gatt_service_id_; + scoped_refptr<BluetoothAdapter> adapter_; +}; + +} // namespace + +class BluetoothGattChromeOSTest : public testing::Test { + public: + BluetoothGattChromeOSTest() + : fake_bluetooth_gatt_service_client_(NULL), + success_callback_count_(0), + error_callback_count_(0) { + } + + virtual void SetUp() { + FakeDBusThreadManager* fake_dbus_thread_manager = new FakeDBusThreadManager; + fake_bluetooth_device_client_ = new FakeBluetoothDeviceClient; + fake_bluetooth_gatt_service_client_ = + new FakeBluetoothGattServiceClient; + fake_bluetooth_gatt_characteristic_client_ = + new FakeBluetoothGattCharacteristicClient; + fake_bluetooth_gatt_descriptor_client_ = + new FakeBluetoothGattDescriptorClient; + fake_dbus_thread_manager->SetBluetoothDeviceClient( + scoped_ptr<BluetoothDeviceClient>( + fake_bluetooth_device_client_)); + fake_dbus_thread_manager->SetBluetoothGattServiceClient( + scoped_ptr<BluetoothGattServiceClient>( + fake_bluetooth_gatt_service_client_)); + fake_dbus_thread_manager->SetBluetoothGattCharacteristicClient( + scoped_ptr<BluetoothGattCharacteristicClient>( + fake_bluetooth_gatt_characteristic_client_)); + fake_dbus_thread_manager->SetBluetoothGattDescriptorClient( + scoped_ptr<BluetoothGattDescriptorClient>( + fake_bluetooth_gatt_descriptor_client_)); + fake_dbus_thread_manager->SetBluetoothAdapterClient( + scoped_ptr<BluetoothAdapterClient>(new FakeBluetoothAdapterClient)); + fake_dbus_thread_manager->SetBluetoothInputClient( + scoped_ptr<BluetoothInputClient>(new FakeBluetoothInputClient)); + fake_dbus_thread_manager->SetBluetoothAgentManagerClient( + scoped_ptr<BluetoothAgentManagerClient>( + new FakeBluetoothAgentManagerClient)); + DBusThreadManager::InitializeForTesting(fake_dbus_thread_manager); + + GetAdapter(); + + adapter_->SetPowered( + true, + base::Bind(&base::DoNothing), + base::Bind(&base::DoNothing)); + ASSERT_TRUE(adapter_->IsPowered()); + } + + virtual void TearDown() { + adapter_ = NULL; + update_sessions_.clear(); + gatt_conn_.reset(); + DBusThreadManager::Shutdown(); + } + + void GetAdapter() { + device::BluetoothAdapterFactory::GetAdapter( + base::Bind(&BluetoothGattChromeOSTest::AdapterCallback, + base::Unretained(this))); + ASSERT_TRUE(adapter_.get() != NULL); + ASSERT_TRUE(adapter_->IsInitialized()); + ASSERT_TRUE(adapter_->IsPresent()); + } + + void AdapterCallback(scoped_refptr<BluetoothAdapter> adapter) { + adapter_ = adapter; + } + + void SuccessCallback() { + ++success_callback_count_; + } + + void ValueCallback(const std::vector<uint8>& value) { + ++success_callback_count_; + last_read_value_ = value; + } + + void GattConnectionCallback(scoped_ptr<BluetoothGattConnection> conn) { + ++success_callback_count_; + gatt_conn_ = conn.Pass(); + } + + void NotifySessionCallback(scoped_ptr<BluetoothGattNotifySession> session) { + ++success_callback_count_; + update_sessions_.push_back(session.release()); + QuitMessageLoop(); + } + + void ErrorCallback() { + ++error_callback_count_; + } + + void DBusErrorCallback(const std::string& error_name, + const std::string& error_message) { + ++error_callback_count_; + } + + void ConnectErrorCallback(BluetoothDevice::ConnectErrorCode error) { + ++error_callback_count_; + } + + protected: + void QuitMessageLoop() { + if (base::MessageLoop::current() && + base::MessageLoop::current()->is_running()) + base::MessageLoop::current()->Quit(); + } + + base::MessageLoop message_loop_; + + FakeBluetoothDeviceClient* fake_bluetooth_device_client_; + FakeBluetoothGattServiceClient* fake_bluetooth_gatt_service_client_; + FakeBluetoothGattCharacteristicClient* + fake_bluetooth_gatt_characteristic_client_; + FakeBluetoothGattDescriptorClient* fake_bluetooth_gatt_descriptor_client_; + scoped_ptr<device::BluetoothGattConnection> gatt_conn_; + ScopedVector<BluetoothGattNotifySession> update_sessions_; + scoped_refptr<BluetoothAdapter> adapter_; + + int success_callback_count_; + int error_callback_count_; + std::vector<uint8> 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. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + EXPECT_EQ(0, observer.gatt_service_added_count_); + EXPECT_EQ(0, observer.gatt_service_removed_count_); + EXPECT_TRUE(observer.last_gatt_service_id_.empty()); + EXPECT_FALSE(observer.last_gatt_service_uuid_.IsValid()); + EXPECT_TRUE(device->GetGattServices().empty()); + + // Expose the fake Heart Rate Service. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + EXPECT_EQ(1, observer.gatt_service_added_count_); + EXPECT_EQ(0, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_EQ(1U, device->GetGattServices().size()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + EXPECT_EQ(service, device->GetGattServices()[0]); + EXPECT_EQ(service, device->GetGattService(service->GetIdentifier())); + + EXPECT_EQ(observer.last_gatt_service_uuid_, service->GetUUID()); + + // Hide the service. + observer.last_gatt_service_uuid_ = BluetoothUUID(); + observer.last_gatt_service_id_.clear(); + fake_bluetooth_gatt_service_client_->HideHeartRateService(); + + EXPECT_EQ(1, observer.gatt_service_added_count_); + EXPECT_EQ(1, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_TRUE(device->GetGattServices().empty()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + + EXPECT_EQ(NULL, device->GetGattService(observer.last_gatt_service_id_)); + + // Expose the service again. + observer.last_gatt_service_uuid_ = BluetoothUUID(); + observer.last_gatt_service_id_.clear(); + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + EXPECT_EQ(2, observer.gatt_service_added_count_); + EXPECT_EQ(1, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_EQ(1U, device->GetGattServices().size()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + + // The object |service| points to should have been deallocated. |device| + // should contain a brand new instance. + service = device->GetGattService(observer.last_gatt_service_id_); + EXPECT_EQ(service, device->GetGattServices()[0]); + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + + EXPECT_EQ(observer.last_gatt_service_uuid_, service->GetUUID()); + + // Remove the device. The observer should be notified of the removed service. + // |device| becomes invalid after this. + observer.last_gatt_service_uuid_ = BluetoothUUID(); + observer.last_gatt_service_id_.clear(); + fake_bluetooth_device_client_->RemoveDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + + EXPECT_EQ(2, observer.gatt_service_added_count_); + EXPECT_EQ(2, observer.gatt_service_removed_count_); + EXPECT_FALSE(observer.last_gatt_service_id_.empty()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + observer.last_gatt_service_uuid_); + EXPECT_EQ( + NULL, adapter_->GetDevice(FakeBluetoothDeviceClient::kLowEnergyAddress)); +} + +TEST_F(BluetoothGattChromeOSTest, GattCharacteristicAddedAndRemoved) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_service_changed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + // 3 characteristics should appear. Only 1 of the characteristics sends + // value changed signals. Service changed should be fired once for + // descriptor added. + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(3U, service->GetCharacteristics().size()); + + // Hide the characteristics. 3 removed signals should be received. + fake_bluetooth_gatt_characteristic_client_->HideHeartRateCharacteristics(); + EXPECT_EQ(8, service_observer.gatt_service_changed_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Re-expose the heart rate characteristics. + fake_bluetooth_gatt_characteristic_client_->ExposeHeartRateCharacteristics( + fake_bluetooth_gatt_service_client_->GetHeartRateServicePath()); + EXPECT_EQ(12, service_observer.gatt_service_changed_count_); + EXPECT_EQ(6, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(3, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(3U, service->GetCharacteristics().size()); + + // Hide the service. All characteristics should disappear. + fake_bluetooth_gatt_service_client_->HideHeartRateService(); + EXPECT_EQ(16, service_observer.gatt_service_changed_count_); + EXPECT_EQ(6, service_observer.gatt_characteristic_added_count_); + EXPECT_EQ(6, service_observer.gatt_characteristic_removed_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); +} + +TEST_F(BluetoothGattChromeOSTest, GattDescriptorAddedAndRemoved) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_service_changed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + + // Only the Heart Rate Measurement characteristic has a descriptor. + EXPECT_EQ(1, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + + BluetoothGattDescriptor* descriptor = characteristic->GetDescriptors()[0]; + EXPECT_FALSE(descriptor->IsLocal()); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + EXPECT_EQ(descriptor->GetUUID(), + service_observer.last_gatt_descriptor_uuid_); + EXPECT_EQ(descriptor->GetIdentifier(), + service_observer.last_gatt_descriptor_id_); + + // Hide the descriptor. + fake_bluetooth_gatt_descriptor_client_->HideDescriptor( + dbus::ObjectPath(descriptor->GetIdentifier())); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + EXPECT_EQ(5, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + // Expose the descriptor again. + service_observer.last_gatt_descriptor_id_.clear(); + service_observer.last_gatt_descriptor_uuid_ = BluetoothUUID(); + fake_bluetooth_gatt_descriptor_client_->ExposeDescriptor( + dbus::ObjectPath(characteristic->GetIdentifier()), + FakeBluetoothGattDescriptorClient:: + kClientCharacteristicConfigurationUUID); + EXPECT_EQ(6, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + EXPECT_EQ(2, service_observer.gatt_descriptor_added_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_removed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + + descriptor = characteristic->GetDescriptors()[0]; + EXPECT_FALSE(descriptor->IsLocal()); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + EXPECT_EQ(descriptor->GetUUID(), service_observer.last_gatt_descriptor_uuid_); + EXPECT_EQ(descriptor->GetIdentifier(), + service_observer.last_gatt_descriptor_id_); +} + +TEST_F(BluetoothGattChromeOSTest, AdapterAddedAfterGattService) { + // This unit test tests that all remote GATT objects are created for D-Bus + // objects that were already exposed. + adapter_ = NULL; + ASSERT_FALSE(device::BluetoothAdapterFactory::HasSharedInstanceForTesting()); + + // Create the fake D-Bus objects. + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + while (!fake_bluetooth_gatt_characteristic_client_->IsHeartRateVisible()) + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(fake_bluetooth_gatt_service_client_->IsHeartRateVisible()); + ASSERT_TRUE(fake_bluetooth_gatt_characteristic_client_->IsHeartRateVisible()); + + // Create the adapter. This should create all the GATT objects. + GetAdapter(); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + EXPECT_EQ(1U, device->GetGattServices().size()); + + BluetoothGattService* service = device->GetGattServices()[0]; + ASSERT_TRUE(service); + EXPECT_FALSE(service->IsLocal()); + EXPECT_TRUE(service->IsPrimary()); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattServiceClient::kHeartRateServiceUUID), + service->GetUUID()); + EXPECT_EQ(service, device->GetGattServices()[0]); + EXPECT_EQ(service, device->GetGattService(service->GetIdentifier())); + EXPECT_FALSE(service->IsLocal()); + EXPECT_EQ(3U, service->GetCharacteristics().size()); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattCharacteristicClient:: + kBodySensorLocationUUID), + characteristic->GetUUID()); + EXPECT_FALSE(characteristic->IsLocal()); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattCharacteristicClient:: + kHeartRateControlPointUUID), + characteristic->GetUUID()); + EXPECT_FALSE(characteristic->IsLocal()); + EXPECT_TRUE(characteristic->GetDescriptors().empty()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + BluetoothUUID(FakeBluetoothGattCharacteristicClient:: + kHeartRateMeasurementUUID), + characteristic->GetUUID()); + EXPECT_FALSE(characteristic->IsLocal()); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + + BluetoothGattDescriptor* descriptor = characteristic->GetDescriptors()[0]; + ASSERT_TRUE(descriptor); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + EXPECT_FALSE(descriptor->IsLocal()); +} + +TEST_F(BluetoothGattChromeOSTest, GattCharacteristicValue) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + // Issue write request to non-writeable characteristics. + service_observer.last_gatt_characteristic_id_.clear(); + service_observer.last_gatt_characteristic_uuid_ = BluetoothUUID(); + + std::vector<uint8> write_value; + write_value.push_back(0x01); + BluetoothGattCharacteristic* characteristic = + service->GetCharacteristic(fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kHeartRateMeasurementUUID, characteristic->GetUUID()); + characteristic->WriteRemoteCharacteristic( + write_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(service_observer.last_gatt_characteristic_id_.empty()); + EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid()); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->WriteRemoteCharacteristic( + write_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(service_observer.last_gatt_characteristic_id_.empty()); + EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid()); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Issue write request to writeable characteristic. The "Body Sensor Location" + // characteristic does not send notifications and WriteValue does not result + // in a CharacteristicValueChanged event, thus no such event should be + // received. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kHeartRateControlPointUUID, characteristic->GetUUID()); + characteristic->WriteRemoteCharacteristic( + write_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(service_observer.last_gatt_characteristic_id_.empty()); + EXPECT_FALSE(service_observer.last_gatt_characteristic_uuid_.IsValid()); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Issue a read request. A successful read results in a + // CharacteristicValueChanged notification. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->ReadRemoteCharacteristic( + base::Bind(&BluetoothGattChromeOSTest::ValueCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_)); +} + +TEST_F(BluetoothGattChromeOSTest, GattCharacteristicProperties) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + BluetoothGattCharacteristic *characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetBodySensorLocationPath().value()); + EXPECT_EQ(BluetoothGattCharacteristic::kPropertyRead, + characteristic->GetProperties()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateControlPointPath().value()); + EXPECT_EQ(BluetoothGattCharacteristic::kPropertyWrite, + characteristic->GetProperties()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + EXPECT_EQ(BluetoothGattCharacteristic::kPropertyNotify, + characteristic->GetProperties()); +} + +TEST_F(BluetoothGattChromeOSTest, GattDescriptorValue) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = adapter_->GetDevice( + FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_service_changed_count_); + EXPECT_EQ(0, service_observer.gatt_descriptor_value_changed_count_); + EXPECT_TRUE(service->GetCharacteristics().empty()); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + + // Only the Heart Rate Measurement characteristic has a descriptor. + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_-> + GetHeartRateMeasurementPath().value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ(1U, characteristic->GetDescriptors().size()); + + BluetoothGattDescriptor* descriptor = characteristic->GetDescriptors()[0]; + EXPECT_FALSE(descriptor->IsLocal()); + EXPECT_EQ(BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid(), + descriptor->GetUUID()); + + std::vector<uint8> desc_value; + desc_value.push_back(1); + desc_value.push_back(0); + + /* The cached value will be empty until the first read request */ + EXPECT_FALSE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_TRUE(descriptor->GetValue().empty()); + + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(last_read_value_.empty()); + + // Read value. GattDescriptorValueChanged event will be sent after a + // successful read. + descriptor->ReadRemoteDescriptor( + base::Bind(&BluetoothGattChromeOSTest::ValueCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(ValuesEqual(last_read_value_, descriptor->GetValue())); + EXPECT_TRUE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_value_changed_count_); + + // Write value. Writes to this descriptor will fail. + desc_value[0] = 0x03; + descriptor->WriteRemoteDescriptor( + desc_value, + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(ValuesEqual(last_read_value_, descriptor->GetValue())); + EXPECT_FALSE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(1, service_observer.gatt_descriptor_value_changed_count_); + + // Read new value. + descriptor->ReadRemoteDescriptor( + base::Bind(&BluetoothGattChromeOSTest::ValueCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_TRUE(ValuesEqual(last_read_value_, descriptor->GetValue())); + EXPECT_FALSE(ValuesEqual(desc_value, descriptor->GetValue())); + EXPECT_EQ(4, service_observer.gatt_service_changed_count_); + EXPECT_EQ(2, service_observer.gatt_descriptor_value_changed_count_); +} + +TEST_F(BluetoothGattChromeOSTest, NotifySessions) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + // Request to start notifications. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + // The operation still hasn't completed but we should have received the first + // notification. + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(update_sessions_.empty()); + + // Send a two more requests, which should get queued. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(update_sessions_.empty()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Run the main loop. The initial call should complete. The queued call should + // succeed immediately. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(3U, update_sessions_.size()); + + // Notifications should be getting sent regularly now. + base::MessageLoop::current()->Run(); + EXPECT_GT(service_observer.gatt_characteristic_value_changed_count_, 1); + + // Stop one of the sessions. The session should become inactive but the + // characteristic should still be notifying. + BluetoothGattNotifySession* session = update_sessions_[0]; + EXPECT_TRUE(session->IsActive()); + session->Stop(base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this))); + EXPECT_EQ(4, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(session->IsActive()); + EXPECT_EQ(characteristic->GetIdentifier(), + session->GetCharacteristicIdentifier()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Delete another session. Characteristic should still be notifying. + update_sessions_.pop_back(); + EXPECT_EQ(2U, update_sessions_.size()); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_FALSE(update_sessions_[0]->IsActive()); + EXPECT_TRUE(update_sessions_[1]->IsActive()); + + // Clear the last session. + update_sessions_.clear(); + EXPECT_TRUE(update_sessions_.empty()); + EXPECT_FALSE(characteristic->IsNotifying()); + + success_callback_count_ = 0; + service_observer.gatt_characteristic_value_changed_count_ = 0; + + // Enable notifications again. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(update_sessions_.empty()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Run the message loop. Notifications should begin. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_EQ(1U, update_sessions_.size()); + EXPECT_TRUE(update_sessions_[0]->IsActive()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Check that notifications are happening. + base::MessageLoop::current()->Run(); + EXPECT_GT(service_observer.gatt_characteristic_value_changed_count_, 1); + + // Request another session. This should return immediately. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(2U, update_sessions_.size()); + EXPECT_TRUE(update_sessions_[0]->IsActive()); + EXPECT_TRUE(update_sessions_[1]->IsActive()); + EXPECT_TRUE(characteristic->IsNotifying()); + + // Hide the characteristic. The sessions should become inactive. + fake_bluetooth_gatt_characteristic_client_->HideHeartRateCharacteristics(); + EXPECT_EQ(2U, update_sessions_.size()); + EXPECT_FALSE(update_sessions_[0]->IsActive()); + EXPECT_FALSE(update_sessions_[1]->IsActive()); +} + +TEST_F(BluetoothGattChromeOSTest, NotifySessionsMadeInactive) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestDeviceObserver observer(adapter_, device); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count_); + + BluetoothGattService* service = + device->GetGattService(observer.last_gatt_service_id_); + + TestGattServiceObserver service_observer(adapter_, device, service); + EXPECT_EQ(0, service_observer.gatt_characteristic_value_changed_count_); + + // Run the message loop so that the characteristics appear. + base::MessageLoop::current()->Run(); + + BluetoothGattCharacteristic* characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + // Send several requests to start notifications. + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + // The operation still hasn't completed but we should have received the first + // notification. + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + // Run the main loop. The initial call should complete. The queued calls + // should succeed immediately. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(4, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_EQ(4U, update_sessions_.size()); + + for (int i = 0; i < 4; i++) + EXPECT_TRUE(update_sessions_[0]->IsActive()); + + // Stop notifications directly through the client. The sessions should get + // marked as inactive. + fake_bluetooth_gatt_characteristic_client_->StopNotify( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath(), + base::Bind(&BluetoothGattChromeOSTest::SuccessCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::DBusErrorCallback, + base::Unretained(this))); + EXPECT_EQ(5, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_EQ(4U, update_sessions_.size()); + + for (int i = 0; i < 4; i++) + EXPECT_FALSE(update_sessions_[0]->IsActive()); + + // It should be possible to restart notifications and the call should reset + // the session count and make a request through the client. + update_sessions_.clear(); + success_callback_count_ = 0; + service_observer.gatt_characteristic_value_changed_count_ = 0; + characteristic->StartNotifySession( + base::Bind(&BluetoothGattChromeOSTest::NotifySessionCallback, + base::Unretained(this)), + base::Bind(&BluetoothGattChromeOSTest::ErrorCallback, + base::Unretained(this))); + + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_TRUE(update_sessions_.empty()); + + base::MessageLoop::current()->Run(); + + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(1, service_observer.gatt_characteristic_value_changed_count_); + EXPECT_TRUE(characteristic->IsNotifying()); + EXPECT_EQ(1U, update_sessions_.size()); + EXPECT_TRUE(update_sessions_[0]->IsActive()); +} + +} // namespace chromeos |