| // 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" |
| |
| #import <Foundation/Foundation.h> |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_path_watcher.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "build/build_config.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_common.h" |
| #include "device/bluetooth/bluetooth_discovery_session.h" |
| #include "device/bluetooth/bluetooth_discovery_session_outcome.h" |
| #import "device/bluetooth/bluetooth_low_energy_device_mac.h" |
| #include "device/bluetooth/bluetooth_low_energy_device_watcher_mac.h" |
| #import "device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h" |
| #import "device/bluetooth/test/mock_bluetooth_central_manager_mac.h" |
| #import "device/bluetooth/test/test_bluetooth_adapter_observer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #import "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) |
| |
| // List of undocumented IOBluetooth APIs used for BluetoothAdapterMac. |
| extern "C" { |
| int IOBluetoothPreferenceGetControllerPowerState(); |
| void IOBluetoothPreferenceSetControllerPowerState(int state); |
| } |
| |
| namespace { |
| |
| const char kTestPropertyListFileName[] = "test_property_list_file.plist"; |
| |
| // |kTestHashAddress| is the hash corresponding to identifier |kTestNSUUID|. |
| const char kTestNSUUID[] = "00000000-1111-2222-3333-444444444444"; |
| const char kTestHashAddress[] = "D1:6F:E3:22:FD:5B"; |
| const int kTestRssi = 0; |
| |
| NSDictionary* CreateTestPropertyListData() { |
| return @{ |
| @"CoreBluetoothCache" : @{ |
| @"00000000-1111-2222-3333-444444444444" : @{ |
| @"DeviceAddress" : @"22-22-22-22-22-22", |
| @"DeviceAddressType" : @1, |
| @"ServiceChangedHandle" : @3, |
| @"ServiceChangeSubscribed" : @0, |
| @"ServiceDiscoveryComplete" : @0 |
| } |
| } |
| }; |
| } |
| |
| bool IsTestDeviceSystemPaired(const std::string& address) { |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace device { |
| |
| class BluetoothAdapterMacTest : public testing::Test { |
| public: |
| class FakeBluetoothLowEnergyDeviceWatcherMac |
| : public BluetoothLowEnergyDeviceWatcherMac { |
| public: |
| FakeBluetoothLowEnergyDeviceWatcherMac( |
| scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner, |
| LowEnergyDeviceListUpdatedCallback callback) |
| : BluetoothLowEnergyDeviceWatcherMac(ui_thread_task_runner, callback), |
| weak_ptr_factory_(this) {} |
| |
| void SimulatePropertyListFileChanged( |
| const base::FilePath& path, |
| const std::string& changed_file_content) { |
| auto expected_file_size = changed_file_content.length(); |
| ASSERT_EQ(static_cast<int>(expected_file_size), |
| base::WriteFile(path, changed_file_content.data(), |
| expected_file_size)); |
| OnPropertyListFileChangedOnFileThread(path, false /* error */); |
| } |
| |
| private: |
| ~FakeBluetoothLowEnergyDeviceWatcherMac() override = default; |
| |
| void Init() override { ReadBluetoothPropertyListFile(); } |
| |
| void ReadBluetoothPropertyListFile() override { |
| low_energy_device_list_updated_callback().Run( |
| ParseBluetoothDevicePropertyListData(CreateTestPropertyListData())); |
| } |
| |
| base::WeakPtrFactory<FakeBluetoothLowEnergyDeviceWatcherMac> |
| weak_ptr_factory_; |
| }; |
| |
| BluetoothAdapterMacTest() |
| : ui_task_runner_(new base::TestSimpleTaskRunner()), |
| adapter_(new BluetoothAdapterMac()), |
| adapter_mac_(static_cast<BluetoothAdapterMac*>(adapter_.get())), |
| observer_(adapter_), |
| callback_count_(0), |
| error_callback_count_(0) { |
| adapter_mac_->SetGetDevicePairedStatusCallbackForTesting( |
| base::BindRepeating(&IsTestDeviceSystemPaired)); |
| adapter_mac_->InitForTest(ui_task_runner_); |
| fake_low_energy_device_watcher_ = |
| base::MakeRefCounted<FakeBluetoothLowEnergyDeviceWatcherMac>( |
| ui_task_runner_, |
| base::BindRepeating( |
| &BluetoothAdapterMac::UpdateKnownLowEnergyDevices, |
| adapter_mac_->weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| test_property_list_file_path_ = |
| temp_dir_.GetPath().AppendASCII(kTestPropertyListFileName); |
| } |
| |
| void TearDown() override { scoped_task_environment_.RunUntilIdle(); } |
| |
| // Helper methods for setup and access to BluetoothAdapterMacTest's members. |
| void PollAdapter() { adapter_mac_->PollAdapter(); } |
| |
| void SetHostControllerPowerFunction(bool powered) { |
| adapter_mac_->SetHostControllerStateFunctionForTesting( |
| base::BindLambdaForTesting([powered] { |
| BluetoothAdapterMac::HostControllerState state; |
| state.classic_powered = powered; |
| return state; |
| })); |
| } |
| |
| 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 peripheral] retain]; |
| } |
| |
| NSDictionary* AdvertisementData() { |
| NSDictionary* advertisement_data = @{ |
| CBAdvertisementDataIsConnectable : @(YES), |
| CBAdvertisementDataServiceDataKey : [NSDictionary dictionary], |
| }; |
| return [advertisement_data retain]; |
| } |
| |
| std::string GetHashAddress(CBPeripheral* peripheral) { |
| return BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral); |
| } |
| |
| int NumDevices() { return adapter_mac_->devices_.size(); } |
| |
| bool DevicePresent(CBPeripheral* peripheral) { |
| BluetoothDevice* device = adapter_mac_->GetDevice( |
| BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral)); |
| return (device != NULL); |
| } |
| |
| 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 = |
| static_cast<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_; } |
| |
| void SetFakeLowEnergyDeviceWatcher() { |
| adapter_mac_->SetLowEnergyDeviceWatcherForTesting( |
| fake_low_energy_device_watcher_); |
| } |
| |
| // Generic callbacks. |
| void Callback() { ++callback_count_; } |
| void ErrorCallback() { ++error_callback_count_; } |
| void DiscoveryErrorCallback(UMABluetoothDiscoverySessionOutcome) { |
| ++error_callback_count_; |
| } |
| |
| protected: |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_; |
| scoped_refptr<BluetoothAdapter> adapter_; |
| BluetoothAdapterMac* adapter_mac_; |
| scoped_refptr<FakeBluetoothLowEnergyDeviceWatcherMac> |
| fake_low_energy_device_watcher_; |
| TestBluetoothAdapterObserver observer_; |
| |
| // Owned by |adapter_mac_|. |
| base::scoped_nsobject<MockCentralManager> mock_central_manager_; |
| |
| int callback_count_; |
| int error_callback_count_; |
| base::ScopedTempDir temp_dir_; |
| base::FilePath test_property_list_file_path_; |
| }; |
| |
| // Test if private IOBluetooth APIs are callable on all supported macOS |
| // versions. |
| TEST_F(BluetoothAdapterMacTest, IOBluetoothPrivateAPIs) { |
| // Obtain current power state, toggle it, and reset it to it's original value. |
| int previous_state = IOBluetoothPreferenceGetControllerPowerState(); |
| IOBluetoothPreferenceSetControllerPowerState(!previous_state); |
| IOBluetoothPreferenceSetControllerPowerState(previous_state); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, Poll) { |
| PollAdapter(); |
| EXPECT_TRUE(ui_task_runner_->HasPendingTask()); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, PollAndChangePower) { |
| // By default the adapter is powered off, check that this expectation matches |
| // reality. |
| EXPECT_FALSE(adapter_mac_->IsPowered()); |
| EXPECT_EQ(0, observer_.powered_changed_count()); |
| |
| SetHostControllerPowerFunction(true); |
| PollAdapter(); |
| EXPECT_TRUE(ui_task_runner_->HasPendingTask()); |
| ui_task_runner_->RunPendingTasks(); |
| EXPECT_EQ(1, observer_.powered_changed_count()); |
| EXPECT_TRUE(observer_.last_powered()); |
| EXPECT_TRUE(adapter_mac_->IsPowered()); |
| |
| SetHostControllerPowerFunction(false); |
| PollAdapter(); |
| EXPECT_TRUE(ui_task_runner_->HasPendingTask()); |
| ui_task_runner_->RunPendingTasks(); |
| EXPECT_EQ(2, observer_.powered_changed_count()); |
| EXPECT_FALSE(observer_.last_powered()); |
| EXPECT_FALSE(adapter_mac_->IsPowered()); |
| } |
| |
| 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(BLUETOOTH_TRANSPORT_LE)); |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_TRUE(ui_task_runner_->HasPendingTask()); |
| ui_task_runner_->RunPendingTasks(); |
| 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(BLUETOOTH_TRANSPORT_LE)); |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_TRUE(ui_task_runner_->HasPendingTask()); |
| ui_task_runner_->RunPendingTasks(); |
| 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_TRUE(ui_task_runner_->HasPendingTask()); |
| ui_task_runner_->RunPendingTasks(); |
| 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(BLUETOOTH_TRANSPORT_LE)); |
| AddDiscoverySession(discovery_filter.get()); |
| EXPECT_TRUE(ui_task_runner_->HasPendingTask()); |
| ui_task_runner_->RunPendingTasks(); |
| 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(BLUETOOTH_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) |
| 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) |
| return; |
| base::scoped_nsobject<NSDictionary> advertisement_data(AdvertisementData()); |
| |
| 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, GetSystemPairedLowEnergyDevice) { |
| SetFakeLowEnergyDeviceWatcher(); |
| ui_task_runner_->RunUntilIdle(); |
| EXPECT_TRUE( |
| adapter_mac_->IsBluetoothLowEnergyDeviceSystemPaired(kTestNSUUID)); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, GetNewlyPairedLowEnergyDevice) { |
| constexpr char kPropertyListFileContentWithAddedDevice[] = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" |
| "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " |
| "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" |
| "<plist version=\"1.0\">" |
| "<dict>" |
| " <key>CoreBluetoothCache</key>" |
| " <dict> " |
| " <key>E7F8589A-A7D9-4B94-9A08-D89076A159F4</key>" |
| " <dict> " |
| " <key>DeviceAddress</key>" |
| " <string>11-11-11-11-11-11</string>" |
| " <key>DeviceAddressType</key>" |
| " <integer>1</integer>" |
| " <key>ServiceChangedHandle</key>" |
| " <integer>3</integer>" |
| " <key>ServiceChangeSubscribed</key>" |
| " <integer>0</integer>" |
| " <key>ServiceDiscoveryComplete</key>" |
| " <integer>0</integer>" |
| " </dict>" |
| " <key>00000000-1111-2222-3333-444444444444</key>" |
| " <dict> " |
| " <key>DeviceAddress</key>" |
| " <string>22-22-22-22-22-22</string>" |
| " <key>DeviceAddressType</key>" |
| " <integer>1</integer>" |
| " <key>ServiceChangedHandle</key>" |
| " <integer>3</integer>" |
| " <key>ServiceChangeSubscribed</key>" |
| " <integer>0</integer>" |
| " <key>ServiceDiscoveryComplete</key>" |
| " <integer>0</integer>" |
| " </dict>" |
| |
| " </dict>" |
| "</dict>" |
| "</plist>"; |
| |
| const char kTestAddedDeviceNSUUID[] = "E7F8589A-A7D9-4B94-9A08-D89076A159F4"; |
| |
| ASSERT_TRUE(SetMockCentralManager(CBCentralManagerStatePoweredOn)); |
| |
| base::scoped_nsobject<CBPeripheral> mock_peripheral_one( |
| CreateMockPeripheral(kTestNSUUID)); |
| ASSERT_TRUE(mock_peripheral_one); |
| |
| LowEnergyDeviceUpdated( |
| mock_peripheral_one, |
| base::scoped_nsobject<NSDictionary>(AdvertisementData()), kTestRssi); |
| |
| base::scoped_nsobject<CBPeripheral> mock_peripheral_two( |
| CreateMockPeripheral(kTestAddedDeviceNSUUID)); |
| ASSERT_TRUE(mock_peripheral_two); |
| |
| LowEnergyDeviceUpdated( |
| mock_peripheral_two, |
| base::scoped_nsobject<NSDictionary>(AdvertisementData()), kTestRssi); |
| observer_.Reset(); |
| |
| // BluetoothAdapterMac only notifies observers of changed devices detected by |
| // BluetoothLowEnergyDeviceWatcherMac if the device has been already known to |
| // the system(i.e. the changed device is in BluetoothAdatper::devices_). As |
| // so, add mock devices prior to setting BluetoothLowenergyDeviceWatcherMac. |
| SetFakeLowEnergyDeviceWatcher(); |
| |
| EXPECT_EQ(1, observer_.device_changed_count()); |
| observer_.Reset(); |
| |
| fake_low_energy_device_watcher_->SimulatePropertyListFileChanged( |
| test_property_list_file_path_, kPropertyListFileContentWithAddedDevice); |
| ui_task_runner_->RunUntilIdle(); |
| EXPECT_EQ(1, observer_.device_changed_count()); |
| EXPECT_TRUE(adapter_mac_->IsBluetoothLowEnergyDeviceSystemPaired( |
| kTestAddedDeviceNSUUID)); |
| } |
| |
| TEST_F(BluetoothAdapterMacTest, NotifyObserverWhenDeviceIsUnpaired) { |
| constexpr char kPropertyListFileContentWithRemovedDevice[] = |
| "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" |
| "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" " |
| "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" |
| "<plist version=\"1.0\">" |
| "<dict>" |
| " <key>CoreBluetoothCache</key>" |
| " <dict> " |
| " </dict>" |
| "</dict>" |
| "</plist>"; |
| |
| if (!SetMockCentralManager(CBCentralManagerStatePoweredOn)) |
| return; |
| |
| base::scoped_nsobject<CBPeripheral> mock_peripheral( |
| CreateMockPeripheral(kTestNSUUID)); |
| if (!mock_peripheral) |
| return; |
| |
| LowEnergyDeviceUpdated( |
| mock_peripheral, base::scoped_nsobject<NSDictionary>(AdvertisementData()), |
| kTestRssi); |
| observer_.Reset(); |
| |
| SetFakeLowEnergyDeviceWatcher(); |
| EXPECT_EQ(1, observer_.device_changed_count()); |
| observer_.Reset(); |
| |
| fake_low_energy_device_watcher_->SimulatePropertyListFileChanged( |
| test_property_list_file_path_, kPropertyListFileContentWithRemovedDevice); |
| ui_task_runner_->RunUntilIdle(); |
| EXPECT_EQ(1, observer_.device_changed_count()); |
| EXPECT_FALSE( |
| adapter_mac_->IsBluetoothLowEnergyDeviceSystemPaired(kTestNSUUID)); |
| } |
| |
| } // namespace device |