| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/bluetooth/web_bluetooth_test_utils.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/callback.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "chrome/browser/bluetooth/chrome_bluetooth_delegate.h" |
| #include "chrome/browser/bluetooth/chrome_bluetooth_delegate_impl_client.h" |
| #include "chrome/browser/bluetooth/web_bluetooth_test_utils.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "device/bluetooth/bluetooth_adapter.h" |
| #include "device/bluetooth/bluetooth_gatt_notify_session.h" |
| #include "device/bluetooth/bluetooth_gatt_service.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_service.h" |
| #include "device/bluetooth/public/cpp/bluetooth_uuid.h" |
| #include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h" |
| #include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" |
| #include "device/bluetooth/test/mock_bluetooth_gatt_service.h" |
| |
| namespace { |
| |
| using ::device::BluetoothAdapter; |
| using ::device::BluetoothGattNotifySession; |
| using ::device::BluetoothGattService; |
| using ::device::BluetoothRemoteGattService; |
| using ::device::BluetoothUUID; |
| using ::device::MockBluetoothGattCharacteristic; |
| using ::device::MockBluetoothGattNotifySession; |
| using ::device::MockBluetoothGattService; |
| |
| } // namespace |
| |
| FakeBluetoothAdapter::FakeBluetoothAdapter() = default; |
| |
| void FakeBluetoothAdapter::SetIsPresent(bool is_present) { |
| is_present_ = is_present; |
| } |
| |
| void FakeBluetoothAdapter::SimulateDeviceAdvertisementReceived( |
| const std::string& device_address, |
| const std::optional<std::string>& advertisement_name) const { |
| for (auto& observer : observers_) { |
| observer.DeviceAdvertisementReceived( |
| device_address, /*device_name=*/std::nullopt, advertisement_name, |
| /*rssi=*/std::nullopt, /*tx_power=*/std::nullopt, |
| /*appearance=*/std::nullopt, |
| /*advertised_uuids=*/{}, /*service_data_map=*/{}, |
| /*manufacturer_data_map=*/{}); |
| } |
| } |
| |
| void FakeBluetoothAdapter::AddObserver( |
| device::BluetoothAdapter::Observer* observer) { |
| device::BluetoothAdapter::AddObserver(observer); |
| } |
| |
| bool FakeBluetoothAdapter::IsPresent() const { |
| return is_present_; |
| } |
| |
| bool FakeBluetoothAdapter::IsPowered() const { |
| return true; |
| } |
| |
| device::BluetoothAdapter::ConstDeviceList FakeBluetoothAdapter::GetDevices() |
| const { |
| device::BluetoothAdapter::ConstDeviceList devices; |
| for (const auto& it : mock_devices_) { |
| devices.push_back(it.get()); |
| } |
| return devices; |
| } |
| |
| device::BluetoothDevice* FakeBluetoothAdapter::GetDevice( |
| const std::string& address) { |
| for (const auto& it : mock_devices_) { |
| if (it->GetAddress() == address) { |
| return it.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| void FakeBluetoothAdapter::StartScanWithFilter( |
| std::unique_ptr<device::BluetoothDiscoveryFilter> filter, |
| base::OnceCallback<void(/*is_error*/ bool, |
| device::UMABluetoothDiscoverySessionOutcome)> |
| callback) { |
| std::move(callback).Run( |
| /*is_error=*/false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); |
| } |
| |
| FakeBluetoothAdapter::~FakeBluetoothAdapter() = default; |
| |
| FakeBluetoothGattCharacteristic::FakeBluetoothGattCharacteristic( |
| MockBluetoothGattService* service, |
| const std::string& identifier, |
| const BluetoothUUID& uuid, |
| Properties properties, |
| Permissions permissions) |
| : testing::NiceMock<MockBluetoothGattCharacteristic>(service, |
| identifier, |
| uuid, |
| properties, |
| permissions), |
| value_({1}) {} |
| |
| FakeBluetoothGattCharacteristic::~FakeBluetoothGattCharacteristic() = default; |
| |
| void FakeBluetoothGattCharacteristic::ReadRemoteCharacteristic( |
| ValueCallback callback) { |
| if (!(GetProperties() & BluetoothGattCharacteristic::PROPERTY_READ)) { |
| std::move(callback).Run(BluetoothGattService::GattErrorCode::kNotPermitted, |
| std::vector<uint8_t>()); |
| return; |
| } |
| if (defer_read_until_notification_start_) { |
| DCHECK(!deferred_read_callback_); |
| deferred_read_callback_ = std::move(callback); |
| return; |
| } |
| std::move(callback).Run(/*error_code=*/std::nullopt, value_); |
| } |
| |
| void FakeBluetoothGattCharacteristic::StartNotifySession( |
| NotifySessionCallback callback, |
| ErrorCallback error_callback) { |
| if (!(GetProperties() & BluetoothGattCharacteristic::PROPERTY_NOTIFY)) { |
| std::move(error_callback) |
| .Run(BluetoothGattService::GattErrorCode::kNotPermitted); |
| return; |
| } |
| auto fake_notify_session = |
| std::make_unique<testing::NiceMock<MockBluetoothGattNotifySession>>( |
| GetWeakPtr()); |
| active_notify_sessions_.insert(fake_notify_session->unique_id()); |
| |
| if (deferred_read_callback_) { |
| // A new value as a result of calling readValue(). |
| std::move(deferred_read_callback_).Run(/*error_code=*/std::nullopt, value_); |
| } |
| |
| if (emit_value_change_at_notification_start_) { |
| BluetoothAdapter* adapter = GetService()->GetDevice()->GetAdapter(); |
| adapter->NotifyGattCharacteristicValueChanged(this, value_); |
| |
| // NotifyGattCharacteristicValueChanged(...) posts a task to notify the |
| // renderer of the change. Do the same for |callback| to ensure |
| // StartNotifySession completes after the value change notification is |
| // received. |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(callback), std::move(fake_notify_session))); |
| } else { |
| // Complete StartNotifySession normally. |
| std::move(callback).Run(std::move(fake_notify_session)); |
| } |
| EXPECT_TRUE(IsNotifying()); |
| } |
| |
| void FakeBluetoothGattCharacteristic::StopNotifySession( |
| BluetoothGattNotifySession::Id session, |
| base::OnceClosure callback) { |
| EXPECT_TRUE(base::Contains(active_notify_sessions_, session)); |
| std::move(callback).Run(); |
| } |
| |
| bool FakeBluetoothGattCharacteristic::IsNotifying() const { |
| return !active_notify_sessions_.empty(); |
| } |
| |
| // Do not call the readValue callback until midway through the completion |
| // of the startNotification callback registration. |
| // https://crbug.com/1153426 |
| void FakeBluetoothGattCharacteristic::DeferReadUntilNotificationStart() { |
| defer_read_until_notification_start_ = true; |
| } |
| |
| // Possibly trigger value characteristicvaluechanged events on the page |
| // during the setup of startNotifications. |
| // https://crbug.com/1153426. |
| void FakeBluetoothGattCharacteristic:: |
| EmitChangeNotificationAtNotificationStart() { |
| emit_value_change_at_notification_start_ = true; |
| } |
| |
| FakeBluetoothGattConnection::FakeBluetoothGattConnection( |
| scoped_refptr<device::BluetoothAdapter> adapter, |
| const std::string& device_address) |
| : testing::NiceMock<device::MockBluetoothGattConnection>(adapter, |
| device_address) {} |
| |
| FakeBluetoothDevice::FakeBluetoothDevice(device::MockBluetoothAdapter* adapter, |
| const std::string& address) |
| : testing::NiceMock<device::MockBluetoothDevice>(adapter, |
| /*bluetooth_class=*/0u, |
| /*name=*/"Test Device", |
| address, |
| /*paired=*/true, |
| /*connected=*/true) {} |
| |
| void FakeBluetoothDevice::CreateGattConnection( |
| device::BluetoothDevice::GattConnectionCallback callback, |
| std::optional<device::BluetoothUUID> service_uuid) { |
| SetConnected(true); |
| gatt_services_discovery_complete_ = true; |
| std::move(callback).Run( |
| std::make_unique<FakeBluetoothGattConnection>(adapter_, GetAddress()), |
| /*error_code=*/std::nullopt); |
| } |
| |
| bool FakeBluetoothDevice::IsGattServicesDiscoveryComplete() const { |
| return gatt_services_discovery_complete_; |
| } |
| |
| BluetoothRemoteGattService* FakeBluetoothDevice::GetGattService( |
| const std::string& identifier) const { |
| return GetMockService(identifier); |
| } |
| |
| std::vector<device::BluetoothRemoteGattService*> |
| FakeBluetoothDevice::GetGattServices() const { |
| return GetMockServices(); |
| } |
| |
| FakeBluetoothChooser::FakeBluetoothChooser( |
| content::BluetoothChooser::EventHandler event_handler, |
| const std::optional<std::string>& device_to_select) |
| : event_handler_(event_handler), device_to_select_(device_to_select) {} |
| |
| FakeBluetoothChooser::~FakeBluetoothChooser() = default; |
| |
| // content::BluetoothChooser implementation: |
| void FakeBluetoothChooser::AddOrUpdateDevice(const std::string& device_id, |
| bool should_update_name, |
| const std::u16string& device_name, |
| bool is_gatt_connected, |
| bool is_paired, |
| int signal_strength_level) { |
| // Select the first device that is added if |device_to_select_| is not |
| // populated. |
| if (!device_to_select_) { |
| event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id); |
| return; |
| } |
| |
| // Otherwise, select the added device if its device ID matches |
| // |device_to_select_|. |
| if (device_to_select_.value() == device_id) { |
| event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id); |
| } |
| } |
| |
| TestBluetoothDelegate::TestBluetoothDelegate() |
| : ChromeBluetoothDelegate( |
| std::make_unique<ChromeBluetoothDelegateImplClient>()) {} |
| |
| TestBluetoothDelegate::~TestBluetoothDelegate() = default; |
| |
| void TestBluetoothDelegate::UseRealChooser() { |
| EXPECT_FALSE(device_to_select_.has_value()); |
| use_real_chooser_ = true; |
| } |
| |
| void TestBluetoothDelegate::SetDeviceToSelect( |
| const std::string& device_address) { |
| EXPECT_FALSE(use_real_chooser_); |
| device_to_select_ = device_address; |
| } |
| |
| std::unique_ptr<content::BluetoothChooser> |
| TestBluetoothDelegate::RunBluetoothChooser( |
| content::RenderFrameHost* frame, |
| const content::BluetoothChooser::EventHandler& event_handler) { |
| if (use_real_chooser_) { |
| return ChromeBluetoothDelegate::RunBluetoothChooser(frame, event_handler); |
| } |
| return std::make_unique<FakeBluetoothChooser>(event_handler, |
| device_to_select_); |
| } |
| |
| std::unique_ptr<content::BluetoothScanningPrompt> |
| TestBluetoothDelegate::ShowBluetoothScanningPrompt( |
| content::RenderFrameHost* frame, |
| const content::BluetoothScanningPrompt::EventHandler& event_handler) { |
| // Simulate that a prompt was accepted; no actual prompt is needed here. |
| event_handler.Run(content::BluetoothScanningPrompt::Event::kAllow); |
| return nullptr; |
| } |
| |
| BluetoothTestContentBrowserClient::BluetoothTestContentBrowserClient() = |
| default; |
| |
| BluetoothTestContentBrowserClient::~BluetoothTestContentBrowserClient() = |
| default; |
| |
| TestBluetoothDelegate* BluetoothTestContentBrowserClient::bluetooth_delegate() { |
| return &bluetooth_delegate_; |
| } |
| |
| content::BluetoothDelegate* |
| BluetoothTestContentBrowserClient::GetBluetoothDelegate() { |
| return &bluetooth_delegate_; |
| } |