| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <string.h> |
| |
| #include <memory> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "content/public/test/browser_test.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/public/cpp/bluetooth_uuid.h" |
| #include "device/bluetooth/test/mock_bluetooth_adapter.h" |
| #include "device/bluetooth/test/mock_bluetooth_device.h" |
| #include "extensions/browser/api/bluetooth/bluetooth_api.h" |
| #include "extensions/browser/api/bluetooth/bluetooth_event_router.h" |
| #include "extensions/browser/api_test_utils.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/test/extension_test_message_listener.h" |
| #include "extensions/test/result_catcher.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using device::BluetoothAdapter; |
| using device::BluetoothDevice; |
| using device::BluetoothDeviceType; |
| using device::BluetoothDiscoverySession; |
| using device::BluetoothUUID; |
| using device::MockBluetoothAdapter; |
| using device::MockBluetoothDevice; |
| |
| namespace utils = extensions::api_test_utils; |
| namespace api = extensions::api; |
| |
| namespace extensions { |
| namespace { |
| |
| using testing::_; |
| using testing::Invoke; |
| |
| static const char* kAdapterAddress = "A1:A2:A3:A4:A5:A6"; |
| static const char* kName = "whatsinaname"; |
| |
| class BluetoothApiTest : public ExtensionApiTest { |
| public: |
| BluetoothApiTest() = default; |
| ~BluetoothApiTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| ExtensionApiTest::SetUpOnMainThread(); |
| empty_extension_ = ExtensionBuilder("Test").Build(); |
| SetUpMockAdapter(); |
| } |
| |
| void TearDownOnMainThread() override { |
| EXPECT_CALL(*mock_adapter_, RemoveObserver(_)); |
| } |
| |
| void SetUpMockAdapter() { |
| // The browser will clean this up when it is torn down |
| mock_adapter_ = new testing::StrictMock<MockBluetoothAdapter>(); |
| event_router()->SetAdapterForTest(mock_adapter_); |
| |
| device1_ = std::make_unique<testing::NiceMock<MockBluetoothDevice>>( |
| mock_adapter_, 0, "d1", "11:12:13:14:15:16", true /* paired */, |
| true /* connected */); |
| device2_ = std::make_unique<testing::NiceMock<MockBluetoothDevice>>( |
| mock_adapter_, 0, "d2", "21:22:23:24:25:26", false /* paired */, |
| false /* connected */); |
| device3_ = std::make_unique<testing::NiceMock<MockBluetoothDevice>>( |
| mock_adapter_, 0, "d3", "31:32:33:34:35:36", false /* paired */, |
| false /* connected */); |
| } |
| |
| void StartScanOverride( |
| const device::BluetoothDiscoveryFilter* filter, |
| base::OnceCallback<void(/*is_error*/ bool, |
| device::UMABluetoothDiscoverySessionOutcome)>& |
| callback) { |
| if (fail_next_call_) { |
| std::move(callback).Run( |
| true, device::UMABluetoothDiscoverySessionOutcome::UNKNOWN); |
| fail_next_call_ = false; |
| return; |
| } |
| std::move(callback).Run( |
| false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); |
| } |
| |
| void StopScanOverride( |
| device::BluetoothAdapter::DiscoverySessionResultCallback callback) { |
| if (fail_next_call_) { |
| std::move(callback).Run( |
| /*is_error=*/true, |
| device::UMABluetoothDiscoverySessionOutcome::UNKNOWN); |
| fail_next_call_ = false; |
| return; |
| } |
| std::move(callback).Run( |
| /*is_error=*/false, |
| device::UMABluetoothDiscoverySessionOutcome::SUCCESS); |
| } |
| |
| void FailNextCall() { fail_next_call_ = true; } |
| |
| template <class T> |
| T* setupFunction(T* function) { |
| function->set_extension(empty_extension_.get()); |
| function->set_has_callback(true); |
| return function; |
| } |
| |
| protected: |
| raw_ptr<testing::StrictMock<MockBluetoothAdapter>, |
| AcrossTasksDanglingUntriaged> |
| mock_adapter_; |
| std::unique_ptr<testing::NiceMock<MockBluetoothDevice>> device1_; |
| std::unique_ptr<testing::NiceMock<MockBluetoothDevice>> device2_; |
| std::unique_ptr<testing::NiceMock<MockBluetoothDevice>> device3_; |
| |
| BluetoothEventRouter* event_router() { |
| return bluetooth_api()->event_router(); |
| } |
| |
| BluetoothAPI* bluetooth_api() { return BluetoothAPI::Get(profile()); } |
| |
| private: |
| scoped_refptr<const Extension> empty_extension_; |
| bool fail_next_call_ = false; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetAdapterState) { |
| EXPECT_CALL(*mock_adapter_, GetAddress()) |
| .WillOnce(testing::Return(kAdapterAddress)); |
| EXPECT_CALL(*mock_adapter_, GetName()) |
| .WillOnce(testing::Return(kName)); |
| EXPECT_CALL(*mock_adapter_, IsPresent()) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(*mock_adapter_, IsPowered()) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_adapter_, IsDiscovering()) |
| .WillOnce(testing::Return(false)); |
| |
| scoped_refptr<api::BluetoothGetAdapterStateFunction> get_adapter_state; |
| get_adapter_state = setupFunction(new api::BluetoothGetAdapterStateFunction); |
| |
| std::optional<base::Value> result = utils::RunFunctionAndReturnSingleResult( |
| get_adapter_state.get(), "[]", profile()); |
| ASSERT_TRUE(result); |
| ASSERT_TRUE(result->is_dict()); |
| auto state = api::bluetooth::AdapterState::FromValue(result->GetDict()); |
| ASSERT_TRUE(state); |
| |
| EXPECT_FALSE(state->available); |
| EXPECT_TRUE(state->powered); |
| EXPECT_FALSE(state->discovering); |
| EXPECT_EQ(kName, state->name); |
| EXPECT_EQ(kAdapterAddress, state->address); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DeviceEvents) { |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| ASSERT_TRUE(LoadExtension( |
| test_data_dir_.AppendASCII("bluetooth/device_events"))); |
| |
| ExtensionTestMessageListener events_received("ready", |
| ReplyBehavior::kWillReply); |
| event_router()->DeviceAdded(mock_adapter_, device1_.get()); |
| event_router()->DeviceAdded(mock_adapter_, device2_.get()); |
| |
| EXPECT_CALL(*device2_, GetName()) |
| .WillRepeatedly( |
| testing::Return(std::optional<std::string>("the real d2"))); |
| EXPECT_CALL(*device2_, GetNameForDisplay()) |
| .WillRepeatedly(testing::Return(u"the real d2")); |
| event_router()->DeviceChanged(mock_adapter_, device2_.get()); |
| |
| event_router()->DeviceAdded(mock_adapter_, device3_.get()); |
| event_router()->DeviceRemoved(mock_adapter_, device1_.get()); |
| EXPECT_TRUE(events_received.WaitUntilSatisfied()); |
| events_received.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, Discovery) { |
| // Simulate start discovery failure |
| EXPECT_CALL(*mock_adapter_, StartScanWithFilter_(_, _)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StartScanOverride)); |
| FailNextCall(); |
| scoped_refptr<api::BluetoothStartDiscoveryFunction> start_function; |
| start_function = setupFunction(new api::BluetoothStartDiscoveryFunction); |
| std::string error( |
| utils::RunFunctionAndReturnError(start_function.get(), "[]", profile())); |
| |
| testing::Mock::VerifyAndClearExpectations(mock_adapter_); |
| // Simulate successful start discovery |
| EXPECT_CALL(*mock_adapter_, StartScanWithFilter_(_, _)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StartScanOverride)); |
| start_function = setupFunction(new api::BluetoothStartDiscoveryFunction); |
| utils::RunFunction(start_function.get(), "[]", profile(), |
| utils::FunctionMode::kNone); |
| |
| testing::Mock::VerifyAndClearExpectations(mock_adapter_); |
| // Simulate stop discovery with a failure |
| EXPECT_CALL(*mock_adapter_, StopScan(_)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StopScanOverride)); |
| FailNextCall(); |
| scoped_refptr<api::BluetoothStopDiscoveryFunction> stop_function; |
| stop_function = setupFunction(new api::BluetoothStopDiscoveryFunction); |
| [[maybe_unused]] auto result = utils::RunFunctionAndReturnSingleResult( |
| stop_function.get(), "[]", profile()); |
| SetUpMockAdapter(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DiscoveryCallback) { |
| EXPECT_CALL(*mock_adapter_, StartScanWithFilter_(_, _)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StartScanOverride)); |
| EXPECT_CALL(*mock_adapter_, StopScan(_)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StopScanOverride)); |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| ExtensionTestMessageListener discovery_started("ready", |
| ReplyBehavior::kWillReply); |
| ASSERT_TRUE(LoadExtension( |
| test_data_dir_.AppendASCII("bluetooth/discovery_callback"))); |
| EXPECT_TRUE(discovery_started.WaitUntilSatisfied()); |
| |
| event_router()->DeviceAdded(mock_adapter_, device1_.get()); |
| |
| discovery_started.Reply("go"); |
| ExtensionTestMessageListener discovery_stopped("ready", |
| ReplyBehavior::kWillReply); |
| EXPECT_CALL(*mock_adapter_, RemoveObserver(_)); |
| EXPECT_TRUE(discovery_stopped.WaitUntilSatisfied()); |
| |
| SetUpMockAdapter(); |
| event_router()->DeviceAdded(mock_adapter_, device2_.get()); |
| discovery_stopped.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DiscoveryInProgress) { |
| EXPECT_CALL(*mock_adapter_, GetAddress()) |
| .WillOnce(testing::Return(kAdapterAddress)); |
| EXPECT_CALL(*mock_adapter_, GetName()) |
| .WillOnce(testing::Return(kName)); |
| EXPECT_CALL(*mock_adapter_, IsPresent()) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_adapter_, IsPowered()) |
| .WillOnce(testing::Return(true)); |
| |
| // Fake that the adapter is discovering |
| EXPECT_CALL(*mock_adapter_, IsDiscovering()) |
| .WillOnce(testing::Return(true)); |
| event_router()->AdapterDiscoveringChanged(mock_adapter_, true); |
| |
| // Cache a device before the extension starts discovering |
| event_router()->DeviceAdded(mock_adapter_, device1_.get()); |
| |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| EXPECT_CALL(*mock_adapter_, StartScanWithFilter_(_, _)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StartScanOverride)); |
| EXPECT_CALL(*mock_adapter_, StopScan(_)) |
| .WillOnce(Invoke(this, &BluetoothApiTest::StopScanOverride)); |
| |
| ExtensionTestMessageListener discovery_started("ready", |
| ReplyBehavior::kWillReply); |
| ASSERT_TRUE(LoadExtension( |
| test_data_dir_.AppendASCII("bluetooth/discovery_in_progress"))); |
| EXPECT_TRUE(discovery_started.WaitUntilSatisfied()); |
| |
| // Only this should be received. No additional notification should be sent for |
| // devices discovered before the discovery session started. |
| event_router()->DeviceAdded(mock_adapter_, device2_.get()); |
| |
| discovery_started.Reply("go"); |
| ExtensionTestMessageListener discovery_stopped("ready", |
| ReplyBehavior::kWillReply); |
| EXPECT_CALL(*mock_adapter_, RemoveObserver(_)); |
| EXPECT_TRUE(discovery_stopped.WaitUntilSatisfied()); |
| |
| SetUpMockAdapter(); |
| // This should never be received. |
| event_router()->DeviceAdded(mock_adapter_, device2_.get()); |
| discovery_stopped.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, OnAdapterStateChanged) { |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| // Load and wait for setup |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE( |
| LoadExtension( |
| test_data_dir_.AppendASCII("bluetooth/on_adapter_state_changed"))); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| EXPECT_CALL(*mock_adapter_, GetAddress()) |
| .WillOnce(testing::Return(kAdapterAddress)); |
| EXPECT_CALL(*mock_adapter_, GetName()) |
| .WillOnce(testing::Return(kName)); |
| EXPECT_CALL(*mock_adapter_, IsPresent()) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(*mock_adapter_, IsPowered()) |
| .WillOnce(testing::Return(false)); |
| EXPECT_CALL(*mock_adapter_, IsDiscovering()) |
| .WillOnce(testing::Return(false)); |
| event_router()->AdapterPoweredChanged(mock_adapter_, false); |
| |
| EXPECT_CALL(*mock_adapter_, GetAddress()) |
| .WillOnce(testing::Return(kAdapterAddress)); |
| EXPECT_CALL(*mock_adapter_, GetName()) |
| .WillOnce(testing::Return(kName)); |
| EXPECT_CALL(*mock_adapter_, IsPresent()) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_adapter_, IsPowered()) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_adapter_, IsDiscovering()) |
| .WillOnce(testing::Return(true)); |
| event_router()->AdapterPresentChanged(mock_adapter_, true); |
| |
| EXPECT_CALL(*mock_adapter_, GetAddress()) |
| .WillOnce(testing::Return(kAdapterAddress)); |
| EXPECT_CALL(*mock_adapter_, GetName()) |
| .WillOnce(testing::Return(kName)); |
| EXPECT_CALL(*mock_adapter_, IsPresent()) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_adapter_, IsPowered()) |
| .WillOnce(testing::Return(true)); |
| EXPECT_CALL(*mock_adapter_, IsDiscovering()) |
| .WillOnce(testing::Return(true)); |
| event_router()->AdapterDiscoveringChanged(mock_adapter_, true); |
| |
| listener.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetDevices) { |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| BluetoothAdapter::ConstDeviceList devices; |
| devices.push_back(device1_.get()); |
| devices.push_back(device2_.get()); |
| |
| EXPECT_CALL(*mock_adapter_, GetDevices()) |
| .Times(1) |
| .WillRepeatedly(testing::Return(devices)); |
| |
| // Load and wait for setup |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE( |
| LoadExtension(test_data_dir_.AppendASCII("bluetooth/get_devices"))); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| listener.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, GetDevice) { |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| EXPECT_CALL(*mock_adapter_, GetDevice(device1_->GetAddress())) |
| .WillOnce(testing::Return(device1_.get())); |
| EXPECT_CALL(*mock_adapter_, GetDevice(device2_->GetAddress())) |
| .Times(1) |
| .WillRepeatedly(testing::Return(static_cast<BluetoothDevice*>(nullptr))); |
| |
| // Load and wait for setup |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE( |
| LoadExtension(test_data_dir_.AppendASCII("bluetooth/get_device"))); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| listener.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(BluetoothApiTest, DeviceInfo) { |
| ResultCatcher catcher; |
| catcher.RestrictToBrowserContext(profile()); |
| |
| // Set up the first device object to reflect a real-world device. |
| BluetoothAdapter::ConstDeviceList devices; |
| |
| EXPECT_CALL(*device1_, GetAddress()) |
| .WillRepeatedly(testing::Return("A4:17:31:00:00:00")); |
| EXPECT_CALL(*device1_, GetName()) |
| .WillRepeatedly( |
| testing::Return(std::optional<std::string>("Chromebook Pixel"))); |
| EXPECT_CALL(*device1_, GetNameForDisplay()) |
| .WillRepeatedly(testing::Return(u"Chromebook Pixel")); |
| EXPECT_CALL(*device1_, GetBluetoothClass()) |
| .WillRepeatedly(testing::Return(0x080104)); |
| EXPECT_CALL(*device1_, GetDeviceType()) |
| .WillRepeatedly(testing::Return(BluetoothDeviceType::COMPUTER)); |
| EXPECT_CALL(*device1_, GetVendorIDSource()) |
| .WillRepeatedly(testing::Return(BluetoothDevice::VENDOR_ID_BLUETOOTH)); |
| EXPECT_CALL(*device1_, GetVendorID()).WillRepeatedly(testing::Return(0x00E0)); |
| EXPECT_CALL(*device1_, GetProductID()) |
| .WillRepeatedly(testing::Return(0x240A)); |
| EXPECT_CALL(*device1_, GetDeviceID()).WillRepeatedly(testing::Return(0x0400)); |
| |
| BluetoothDevice::UUIDSet uuids; |
| uuids.insert(BluetoothUUID("1105")); |
| uuids.insert(BluetoothUUID("1106")); |
| |
| EXPECT_CALL(*device1_, GetUUIDs()).WillOnce(testing::Return(uuids)); |
| |
| devices.push_back(device1_.get()); |
| |
| // Leave the second largely empty so we can check a device without |
| // available information. |
| devices.push_back(device2_.get()); |
| |
| EXPECT_CALL(*mock_adapter_, GetDevices()) |
| .Times(1) |
| .WillRepeatedly(testing::Return(devices)); |
| |
| // Load and wait for setup |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE( |
| LoadExtension(test_data_dir_.AppendASCII("bluetooth/device_info"))); |
| EXPECT_TRUE(listener.WaitUntilSatisfied()); |
| |
| listener.Reply("go"); |
| |
| EXPECT_TRUE(catcher.GetNextResult()) << catcher.message(); |
| } |
| |
| } // namespace |
| } // namespace extensions |