1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/memory/ref_counted.h"
6 #include "base/test/test_simple_task_runner.h"
7 #include "device/bluetooth/bluetooth_adapter.h"
8 #include "device/bluetooth/bluetooth_adapter_mac.h"
9 #include "device/bluetooth/bluetooth_discovery_session.h"
10 #include "device/bluetooth/bluetooth_discovery_session_outcome.h"
11 #include "device/bluetooth/bluetooth_low_energy_device_mac.h"
12 #include "device/bluetooth/test/mock_bluetooth_central_manager_mac.h"
13 #include "testing/gtest/include/gtest/gtest.h"
14 #include "third_party/ocmock/OCMock/OCMock.h"
17 #import <CoreBluetooth/CoreBluetooth.h>
18 #else // !defined(OS_IOS)
19 #import <IOBluetooth/IOBluetooth.h>
20 #endif // defined(OS_IOS)
22 #import <Foundation/Foundation.h>
25 // |kTestHashAddress| is the hash corresponding to identifier |kTestNSUUID|.
26 NSString* const kTestNSUUID = @"00000000-1111-2222-3333-444444444444";
27 const std::string kTestHashAddress = "D1:6F:E3:22:FD:5B";
28 const int kTestRssi = 0;
33 class BluetoothAdapterMacTest : public testing::Test {
35 BluetoothAdapterMacTest()
36 : ui_task_runner_(new base::TestSimpleTaskRunner()),
37 adapter_(new BluetoothAdapterMac()),
38 adapter_mac_(static_cast<BluetoothAdapterMac*>(adapter_.get())),
40 error_callback_count_(0) {
41 adapter_mac_->InitForTest(ui_task_runner_);
44 // Helper methods for setup and access to BluetoothAdapterMacTest's members.
45 void PollAdapter() { adapter_mac_->PollAdapter(); }
47 void LowEnergyDeviceUpdated(CBPeripheral* peripheral,
48 NSDictionary* advertisement_data,
50 adapter_mac_->LowEnergyDeviceUpdated(peripheral, advertisement_data, rssi);
53 BluetoothDevice* GetDevice(const std::string& address) {
54 return adapter_->GetDevice(address);
57 CBPeripheral* CreateMockPeripheral(NSString* identifier) {
58 if (!BluetoothAdapterMac::IsLowEnergyAvailable()) {
59 LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
62 Class peripheral_class = NSClassFromString(@"CBPeripheral");
64 [[OCMockObject mockForClass:[peripheral_class class]] retain];
65 [static_cast<CBPeripheral*>([[mock_peripheral stub]
66 andReturnValue:@(CBPeripheralStateDisconnected)])
67 performSelector:@selector(state)];
68 [[[mock_peripheral stub] andReturn:[NSString string]] name];
69 Class uuid_class = NSClassFromString(@"NSUUID");
70 [[[mock_peripheral stub]
71 andReturn:[[uuid_class performSelector:@selector(UUID)]
72 performSelector:@selector(initWithUUIDString:)
73 withObject:identifier]] identifier];
75 return mock_peripheral;
78 NSDictionary* CreateAdvertisementData() {
79 NSDictionary* advertisement_data = @{
80 @"CBAdvertisementDataIsConnectable" : @(YES),
81 @"CBAdvertisementDataServiceDataKey" : [NSDictionary dictionary],
83 [advertisement_data retain];
84 return advertisement_data;
87 std::string GetHashAddress(CBPeripheral* peripheral) {
88 return BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral);
91 void SetDeviceTimeGreaterThanTimeout(BluetoothLowEnergyDeviceMac* device) {
92 device->last_update_time_.reset([[NSDate
93 dateWithTimeInterval:-(BluetoothAdapterMac::kDiscoveryTimeoutSec + 1)
94 sinceDate:[NSDate date]] retain]);
97 void AddLowEnergyDevice(BluetoothLowEnergyDeviceMac* device) {
98 adapter_mac_->devices_[device->GetAddress()] = device;
101 int NumDevices() { return adapter_mac_->devices_.size(); }
103 bool DevicePresent(CBPeripheral* peripheral) {
104 BluetoothDevice* device = adapter_mac_->GetDevice(
105 BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral));
106 return (device != NULL);
109 void RemoveTimedOutDevices() { adapter_mac_->RemoveTimedOutDevices(); }
111 bool SetMockCentralManager(CBCentralManagerState desired_state) {
112 if (!BluetoothAdapterMac::IsLowEnergyAvailable()) {
113 LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test.";
116 mock_central_manager_ = [[MockCentralManager alloc] init];
117 [mock_central_manager_ setState:desired_state];
118 adapter_mac_->SetCentralManagerForTesting(mock_central_manager_);
122 void AddDiscoverySession(BluetoothDiscoveryFilter* discovery_filter) {
123 adapter_mac_->AddDiscoverySession(
125 base::Bind(&BluetoothAdapterMacTest::Callback, base::Unretained(this)),
126 base::Bind(&BluetoothAdapterMacTest::DiscoveryErrorCallback,
127 base::Unretained(this)));
130 void RemoveDiscoverySession(BluetoothDiscoveryFilter* discovery_filter) {
131 adapter_mac_->RemoveDiscoverySession(
133 base::Bind(&BluetoothAdapterMacTest::Callback, base::Unretained(this)),
134 base::Bind(&BluetoothAdapterMacTest::DiscoveryErrorCallback,
135 base::Unretained(this)));
138 int NumDiscoverySessions() { return adapter_mac_->num_discovery_sessions_; }
140 // Generic callbacks.
141 void Callback() { ++callback_count_; }
142 void ErrorCallback() { ++error_callback_count_; }
143 void DiscoveryErrorCallback(UMABluetoothDiscoverySessionOutcome) {
144 ++error_callback_count_;
148 scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_;
149 scoped_refptr<BluetoothAdapter> adapter_;
150 BluetoothAdapterMac* adapter_mac_;
152 // Owned by |adapter_mac_|.
153 id mock_central_manager_ = NULL;
156 int error_callback_count_;
159 TEST_F(BluetoothAdapterMacTest, Poll) {
161 EXPECT_FALSE(ui_task_runner_->GetPendingTasks().empty());
164 TEST_F(BluetoothAdapterMacTest, AddDiscoverySessionWithLowEnergyFilter) {
165 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
167 EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
168 EXPECT_EQ(0, NumDiscoverySessions());
170 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
171 new BluetoothDiscoveryFilter(
172 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
173 AddDiscoverySession(discovery_filter.get());
174 EXPECT_EQ(1, callback_count_);
175 EXPECT_EQ(0, error_callback_count_);
176 EXPECT_EQ(1, NumDiscoverySessions());
178 // Check that adding a discovery session resulted in
179 // scanForPeripheralsWithServices being called on the Central Manager.
180 EXPECT_EQ(1, [mock_central_manager_ scanForPeripheralsCallCount]);
183 // TODO(krstnmnlsn): Test changing the filter when adding the second discovery
184 // session (once we have that ability).
185 TEST_F(BluetoothAdapterMacTest, AddSecondDiscoverySessionWithLowEnergyFilter) {
186 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
188 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
189 new BluetoothDiscoveryFilter(
190 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
191 AddDiscoverySession(discovery_filter.get());
192 EXPECT_EQ(1, callback_count_);
193 EXPECT_EQ(0, error_callback_count_);
194 EXPECT_EQ(1, NumDiscoverySessions());
196 // We replaced the success callback handed to AddDiscoverySession, so
197 // |adapter_mac_| should remain in a discovering state indefinitely.
198 EXPECT_TRUE(adapter_mac_->IsDiscovering());
200 AddDiscoverySession(discovery_filter.get());
201 EXPECT_EQ(2, [mock_central_manager_ scanForPeripheralsCallCount]);
202 EXPECT_EQ(2, callback_count_);
203 EXPECT_EQ(0, error_callback_count_);
204 EXPECT_EQ(2, NumDiscoverySessions());
207 TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilter) {
208 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
210 EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
212 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
213 new BluetoothDiscoveryFilter(
214 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
215 AddDiscoverySession(discovery_filter.get());
216 EXPECT_EQ(1, callback_count_);
217 EXPECT_EQ(0, error_callback_count_);
218 EXPECT_EQ(1, NumDiscoverySessions());
220 EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
221 RemoveDiscoverySession(discovery_filter.get());
222 EXPECT_EQ(2, callback_count_);
223 EXPECT_EQ(0, error_callback_count_);
224 EXPECT_EQ(0, NumDiscoverySessions());
226 // Check that removing the discovery session resulted in stopScan being called
227 // on the Central Manager.
228 EXPECT_EQ(1, [mock_central_manager_ stopScanCallCount]);
231 TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilterFail) {
232 if (!SetMockCentralManager(CBCentralManagerStatePoweredOn))
234 EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
235 EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
236 EXPECT_EQ(0, NumDiscoverySessions());
238 scoped_ptr<BluetoothDiscoveryFilter> discovery_filter(
239 new BluetoothDiscoveryFilter(
240 BluetoothDiscoveryFilter::Transport::TRANSPORT_LE));
241 RemoveDiscoverySession(discovery_filter.get());
242 EXPECT_EQ(0, callback_count_);
243 EXPECT_EQ(1, error_callback_count_);
244 EXPECT_EQ(0, NumDiscoverySessions());
246 // Check that stopScan was not called.
247 EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
250 TEST_F(BluetoothAdapterMacTest, CheckGetPeripheralHashAddress) {
251 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
252 if (mock_peripheral.get() == nil)
254 EXPECT_EQ(kTestHashAddress, GetHashAddress(mock_peripheral));
257 TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedNewDevice) {
258 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
259 if (mock_peripheral.get() == nil)
261 base::scoped_nsobject<NSDictionary> advertisement_data(
262 CreateAdvertisementData());
264 EXPECT_EQ(0, NumDevices());
265 EXPECT_FALSE(DevicePresent(mock_peripheral));
266 LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
267 EXPECT_EQ(1, NumDevices());
268 EXPECT_TRUE(DevicePresent(mock_peripheral));
271 TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedOldDevice) {
272 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
273 if (mock_peripheral.get() == nil)
275 base::scoped_nsobject<NSDictionary> advertisement_data(
276 CreateAdvertisementData());
278 // Update the device for the first time and check it was correctly added to
280 EXPECT_EQ(0, NumDevices());
281 EXPECT_FALSE(DevicePresent(mock_peripheral));
282 LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
283 EXPECT_EQ(1, NumDevices());
284 EXPECT_TRUE(DevicePresent(mock_peripheral));
285 // Search for the device by the address corresponding to |kTestNSUUID|.
286 BluetoothDeviceMac* device =
287 static_cast<BluetoothDeviceMac*>(GetDevice(kTestHashAddress));
288 base::scoped_nsobject<NSDate> first_update_time(
289 [device->GetLastUpdateTime() retain]);
291 // Update the device a second time. The device should be updated in
292 // |devices_| so check the time returned by GetLastUpdateTime() has increased.
293 LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
294 EXPECT_EQ(1, NumDevices());
295 EXPECT_TRUE(DevicePresent(mock_peripheral));
296 device = static_cast<BluetoothDeviceMac*>(GetDevice(kTestHashAddress));
297 EXPECT_TRUE([device->GetLastUpdateTime() compare:first_update_time] ==
298 NSOrderedDescending);
301 TEST_F(BluetoothAdapterMacTest, UpdateDevicesRemovesLowEnergyDevice) {
302 base::scoped_nsobject<id> mock_peripheral(CreateMockPeripheral(kTestNSUUID));
303 if (mock_peripheral.get() == nil)
305 base::scoped_nsobject<NSDictionary> advertisement_data(
306 CreateAdvertisementData());
308 BluetoothLowEnergyDeviceMac* device = new BluetoothLowEnergyDeviceMac(
309 adapter_mac_, mock_peripheral, advertisement_data, kTestRssi);
310 SetDeviceTimeGreaterThanTimeout(device);
312 EXPECT_EQ(0, NumDevices());
313 AddLowEnergyDevice(device);
314 EXPECT_EQ(1, NumDevices());
315 EXPECT_TRUE(DevicePresent(mock_peripheral));
317 // Check that object pointed to by |device| is deleted by the adapter.
318 RemoveTimedOutDevices();
319 EXPECT_EQ(0, NumDevices());
320 EXPECT_FALSE(DevicePresent(mock_peripheral));
323 } // namespace device