| // Copyright 2013 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_adapter_mac.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "build/build_config.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_discovery_session.h" |
| #include "device/bluetooth/bluetooth_discovery_session_outcome.h" |
| #include "device/bluetooth/bluetooth_low_energy_device_mac.h" |
| #include "device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h" |
| #include "device/bluetooth/test/mock_bluetooth_central_manager_mac.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/ocmock/OCMock/OCMock.h" |
| |
| #if defined(OS_IOS) |
| #import <CoreBluetooth/CoreBluetooth.h> |
| #else // !defined(OS_IOS) |
| #import <IOBluetooth/IOBluetooth.h> |
| #endif // defined(OS_IOS) |
| |
| #import <Foundation/Foundation.h> |
| |
| namespace { |
| // |kTestHashAddress| is the hash corresponding to identifier |kTestNSUUID|. |
| const char* const kTestNSUUID = "00000000-1111-2222-3333-444444444444"; |
| const std::string kTestHashAddress = "D1:6F:E3:22:FD:5B"; |
| const int kTestRssi = 0; |
| } // namespace |
| |
| namespace device { |
| |
| class BluetoothAdapterMacTest : public testing::Test { |
| public: |
| BluetoothAdapterMacTest() |
| : ui_task_runner_(new base::TestSimpleTaskRunner()), |
| adapter_(new BluetoothAdapterMac()), |
| adapter_mac_(static_cast<BluetoothAdapterMac*>(adapter_.get())), |
| callback_count_(0), |
| error_callback_count_(0) { |
| adapter_mac_->InitForTest(ui_task_runner_); |
| } |
| |
| // Helper methods for setup and access to BluetoothAdapterMacTest's members. |
| void PollAdapter() { adapter_mac_->PollAdapter(); } |
| |
| void LowEnergyDeviceUpdated(CBPeripheral* peripheral, |
| NSDictionary* advertisement_data, |
| int rssi) { |
| adapter_mac_->LowEnergyDeviceUpdated(peripheral, advertisement_data, rssi); |
| } |
| |
| BluetoothDevice* GetDevice(const std::string& address) { |
| return adapter_->GetDevice(address); |
| } |
| |
| CBPeripheral* CreateMockPeripheral(const char* identifier) { |
| if (!BluetoothAdapterMac::IsLowEnergyAvailable()) { |
| LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; |
| return nil; |
| } |
| base::scoped_nsobject<MockCBPeripheral> mock_peripheral( |
| [[MockCBPeripheral alloc] initWithUTF8StringIdentifier:identifier]); |
| return [mock_peripheral.get().peripheral retain]; |
| } |
| |
| NSDictionary* CreateAdvertisementData() { |
| NSDictionary* advertisement_data = @{ |
| CBAdvertisementDataIsConnectable : @(YES), |
| CBAdvertisementDataServiceDataKey : [NSDictionary dictionary], |
| }; |
| return [advertisement_data retain]; |
| } |
| |
| std::string GetHashAddress(CBPeripheral* peripheral) { |
| return BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral); |
| } |
| |
| void SetDeviceTimeGreaterThanTimeout(BluetoothLowEnergyDeviceMac* device) { |
| device->last_update_time_.reset([[NSDate |
| dateWithTimeInterval:-(BluetoothAdapterMac::kDiscoveryTimeoutSec + 1) |
| sinceDate:[NSDate date]] retain]); |
| } |
| |
| void AddLowEnergyDevice(BluetoothLowEnergyDeviceMac* device) { |
| adapter_mac_->devices_.set(device->GetAddress(), |
| std::unique_ptr<BluetoothDevice>(device)); |
| } |
| |
| int NumDevices() { return adapter_mac_->devices_.size(); } |
| |
| bool DevicePresent(CBPeripheral* peripheral) { |
| BluetoothDevice* device = adapter_mac_->GetDevice( |
| BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral)); |
| return (device != NULL); |
| } |
| |
| void RemoveTimedOutDevices() { adapter_mac_->RemoveTimedOutDevices(); } |
| |
| bool SetMockCentralManager(CBCentralManagerState desired_state) { |
| if (!BluetoothAdapterMac::IsLowEnergyAvailable()) { |
| LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; |
| return false; |
| } |
| mock_central_manager_.reset([[MockCentralManager alloc] init]); |
| [mock_central_manager_ setState:desired_state]; |
| CBCentralManager* centralManager = |
| (CBCentralManager*)mock_central_manager_.get(); |
| adapter_mac_->SetCentralManagerForTesting(centralManager); |
| return true; |
| } |
| |
| void AddDiscoverySession(BluetoothDiscoveryFilter* discovery_filter) { |
| adapter_mac_->AddDiscoverySession( |
| discovery_filter, |
| base::Bind(&BluetoothAdapterMacTest::Callback, base::Unretained(this)), |
| base::Bind(&BluetoothAdapterMacTest::DiscoveryErrorCallback, |
| base::Unretained(this))); |
| } |
| |
| void RemoveDiscoverySession(BluetoothDiscoveryFilter* discovery_filter) { |
| adapter_mac_->RemoveDiscoverySession( |
| discovery_filter, |
| base::Bind(&BluetoothAdapterMacTest::Callback, base::Unretained(this)), |
| base::Bind(&BluetoothAdapterMacTest::DiscoveryErrorCallback, |
| base::Unretained(this))); |
| } |
| |
| int NumDiscoverySessions() { return adapter_mac_->num_discovery_sessions_; } |
| |
| // Generic callbacks. |
| void Callback() { ++callback_count_; } |
| void ErrorCallback() { ++error_callback_count_; } |
| void DiscoveryErrorCallback(UMABluetoothDiscoverySessionOutcome) { |
| ++error_callback_count_; |
| } |
| |
| protected: |
| scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_; |
| scoped_refptr<BluetoothAdapter> adapter_; |
| BluetoothAdapterMac* adapter_mac_; |
| |
| // Owned by |adapter_mac_|. |
| base::scoped_nsobject<MockCentralManager> mock_central_manager_; |
| |
| int callback_count_; |
| int error_callback_count_; |
| }; |
| |
| TEST_F(BluetoothAdapterMacTest, Poll) { |
| PollAdapter(); |
| EXPECT_FALSE(ui_task_runner_->GetPendingTasks().empty()); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, AddDiscoverySessionWithLowEnergyFilter) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]); |
| EXPECT_EQ(0, NumDiscoverySessions()); |
| |
| std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter( |
| new BluetoothDiscoveryFilter( |
| BluetoothDiscoveryFilter::Transport::TRANSPORT_LE)); |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_EQ(1, callback_count_); |
| EXPECT_EQ(0, error_callback_count_); |
| EXPECT_EQ(1, NumDiscoverySessions()); |
| |
| // Check that adding a discovery session resulted in |
| // scanForPeripheralsWithServices being called on the Central Manager. |
| EXPECT_EQ(1, [mock_central_manager_ scanForPeripheralsCallCount]); |
| } |
| |
| // TODO(krstnmnlsn): Test changing the filter when adding the second discovery |
| // session (once we have that ability). |
| TEST_F(BluetoothAdapterMacTest, AddSecondDiscoverySessionWithLowEnergyFilter) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter( |
| new BluetoothDiscoveryFilter( |
| BluetoothDiscoveryFilter::Transport::TRANSPORT_LE)); |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_EQ(1, callback_count_); |
| EXPECT_EQ(0, error_callback_count_); |
| EXPECT_EQ(1, NumDiscoverySessions()); |
| |
| // We replaced the success callback handed to AddDiscoverySession, so |
| // |adapter_mac_| should remain in a discovering state indefinitely. |
| EXPECT_TRUE(adapter_mac_->IsDiscovering()); |
| |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_EQ(2, [mock_central_manager_ scanForPeripheralsCallCount]); |
| EXPECT_EQ(2, callback_count_); |
| EXPECT_EQ(0, error_callback_count_); |
| EXPECT_EQ(2, NumDiscoverySessions()); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilter) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]); |
| |
| std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter( |
| new BluetoothDiscoveryFilter( |
| BluetoothDiscoveryFilter::Transport::TRANSPORT_LE)); |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_EQ(1, callback_count_); |
| EXPECT_EQ(0, error_callback_count_); |
| EXPECT_EQ(1, NumDiscoverySessions()); |
| |
| EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]); |
| RemoveDiscoverySession(discovery_filter.get()); |
| EXPECT_EQ(2, callback_count_); |
| EXPECT_EQ(0, error_callback_count_); |
| EXPECT_EQ(0, NumDiscoverySessions()); |
| |
| // Check that removing the discovery session resulted in stopScan being called |
| // on the Central Manager. |
| EXPECT_EQ(1, [mock_central_manager_ stopScanCallCount]); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilterFail) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]); |
| EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]); |
| EXPECT_EQ(0, NumDiscoverySessions()); |
| |
| std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter( |
| new BluetoothDiscoveryFilter( |
| BluetoothDiscoveryFilter::Transport::TRANSPORT_LE)); |
| RemoveDiscoverySession(discovery_filter.get()); |
| EXPECT_EQ(0, callback_count_); |
| EXPECT_EQ(1, error_callback_count_); |
| EXPECT_EQ(0, NumDiscoverySessions()); |
| |
| // Check that stopScan was not called. |
| EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, CheckGetPeripheralHashAddress) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| base::scoped_nsobject<CBPeripheral> mock_peripheral( |
| CreateMockPeripheral(kTestNSUUID)); |
| if (mock_peripheral.get() == nil) |
| return; |
| EXPECT_EQ(kTestHashAddress, GetHashAddress(mock_peripheral)); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedNewDevice) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| base::scoped_nsobject<CBPeripheral> mock_peripheral( |
| CreateMockPeripheral(kTestNSUUID)); |
| if (mock_peripheral.get() == nil) |
| return; |
| base::scoped_nsobject<NSDictionary> advertisement_data( |
| CreateAdvertisementData()); |
| |
| EXPECT_EQ(0, NumDevices()); |
| EXPECT_FALSE(DevicePresent(mock_peripheral)); |
| LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi); |
| EXPECT_EQ(1, NumDevices()); |
| EXPECT_TRUE(DevicePresent(mock_peripheral)); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedOldDevice) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| base::scoped_nsobject<CBPeripheral> mock_peripheral( |
| CreateMockPeripheral(kTestNSUUID)); |
| if (mock_peripheral.get() == nil) |
| return; |
| base::scoped_nsobject<NSDictionary> advertisement_data( |
| CreateAdvertisementData()); |
| |
| // Update the device for the first time and check it was correctly added to |
| // |devices_|. |
| EXPECT_EQ(0, NumDevices()); |
| EXPECT_FALSE(DevicePresent(mock_peripheral)); |
| LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi); |
| EXPECT_EQ(1, NumDevices()); |
| EXPECT_TRUE(DevicePresent(mock_peripheral)); |
| // Search for the device by the address corresponding to |kTestNSUUID|. |
| BluetoothDeviceMac* device = |
| static_cast<BluetoothDeviceMac*>(GetDevice(kTestHashAddress)); |
| base::scoped_nsobject<NSDate> first_update_time( |
| [device->GetLastUpdateTime() retain]); |
| |
| // Update the device a second time. The device should be updated in |
| // |devices_| so check the time returned by GetLastUpdateTime() has increased. |
| LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi); |
| EXPECT_EQ(1, NumDevices()); |
| EXPECT_TRUE(DevicePresent(mock_peripheral)); |
| device = static_cast<BluetoothDeviceMac*>(GetDevice(kTestHashAddress)); |
| EXPECT_TRUE([device->GetLastUpdateTime() compare:first_update_time] == |
| NSOrderedDescending); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, UpdateDevicesRemovesLowEnergyDevice) { |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| base::scoped_nsobject<CBPeripheral> mock_peripheral( |
| CreateMockPeripheral(kTestNSUUID)); |
| if (mock_peripheral.get() == nil) |
| return; |
| base::scoped_nsobject<NSDictionary> advertisement_data( |
| CreateAdvertisementData()); |
| |
| BluetoothLowEnergyDeviceMac* device = new BluetoothLowEnergyDeviceMac( |
| adapter_mac_, mock_peripheral, advertisement_data, kTestRssi); |
| SetDeviceTimeGreaterThanTimeout(device); |
| |
| EXPECT_EQ(0, NumDevices()); |
| AddLowEnergyDevice(device); |
| EXPECT_EQ(1, NumDevices()); |
| EXPECT_TRUE(DevicePresent(mock_peripheral)); |
| |
| // Check that object pointed to by |device| is deleted by the adapter. |
| RemoveTimedOutDevices(); |
| EXPECT_EQ(0, NumDevices()); |
| EXPECT_FALSE(DevicePresent(mock_peripheral)); |
| } |
| |
| } // namespace device |