Zelin Liu | 44c1658 | 2024-09-12 09:37:31 | [diff] [blame] | 1 | // Copyright 2024 The Chromium Authors |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "chrome/browser/bluetooth/web_bluetooth_test_utils.h" |
| 6 | |
| 7 | #include <memory> |
| 8 | #include <optional> |
| 9 | #include <string> |
| 10 | #include <utility> |
| 11 | |
| 12 | #include "base/containers/contains.h" |
| 13 | #include "base/functional/callback.h" |
| 14 | #include "base/task/single_thread_task_runner.h" |
| 15 | #include "chrome/browser/bluetooth/chrome_bluetooth_delegate.h" |
| 16 | #include "chrome/browser/bluetooth/chrome_bluetooth_delegate_impl_client.h" |
| 17 | #include "chrome/browser/bluetooth/web_bluetooth_test_utils.h" |
| 18 | #include "content/public/browser/render_frame_host.h" |
| 19 | #include "device/bluetooth/bluetooth_adapter.h" |
| 20 | #include "device/bluetooth/bluetooth_gatt_notify_session.h" |
| 21 | #include "device/bluetooth/bluetooth_gatt_service.h" |
| 22 | #include "device/bluetooth/bluetooth_remote_gatt_service.h" |
| 23 | #include "device/bluetooth/public/cpp/bluetooth_uuid.h" |
| 24 | #include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h" |
| 25 | #include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" |
| 26 | #include "device/bluetooth/test/mock_bluetooth_gatt_service.h" |
| 27 | |
| 28 | namespace { |
| 29 | |
| 30 | using ::device::BluetoothAdapter; |
| 31 | using ::device::BluetoothGattNotifySession; |
| 32 | using ::device::BluetoothGattService; |
| 33 | using ::device::BluetoothRemoteGattService; |
| 34 | using ::device::BluetoothUUID; |
| 35 | using ::device::MockBluetoothGattCharacteristic; |
| 36 | using ::device::MockBluetoothGattNotifySession; |
| 37 | using ::device::MockBluetoothGattService; |
| 38 | |
| 39 | } // namespace |
| 40 | |
| 41 | FakeBluetoothAdapter::FakeBluetoothAdapter() = default; |
| 42 | |
| 43 | void FakeBluetoothAdapter::SetIsPresent(bool is_present) { |
| 44 | is_present_ = is_present; |
| 45 | } |
| 46 | |
| 47 | void FakeBluetoothAdapter::SimulateDeviceAdvertisementReceived( |
| 48 | const std::string& device_address, |
| 49 | const std::optional<std::string>& advertisement_name) const { |
| 50 | for (auto& observer : observers_) { |
| 51 | observer.DeviceAdvertisementReceived( |
| 52 | device_address, /*device_name=*/std::nullopt, advertisement_name, |
| 53 | /*rssi=*/std::nullopt, /*tx_power=*/std::nullopt, |
| 54 | /*appearance=*/std::nullopt, |
| 55 | /*advertised_uuids=*/{}, /*service_data_map=*/{}, |
| 56 | /*manufacturer_data_map=*/{}); |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | void FakeBluetoothAdapter::AddObserver( |
| 61 | device::BluetoothAdapter::Observer* observer) { |
| 62 | device::BluetoothAdapter::AddObserver(observer); |
| 63 | } |
| 64 | |
| 65 | bool FakeBluetoothAdapter::IsPresent() const { |
| 66 | return is_present_; |
| 67 | } |
| 68 | |
| 69 | bool FakeBluetoothAdapter::IsPowered() const { |
| 70 | return true; |
| 71 | } |
| 72 | |
| 73 | device::BluetoothAdapter::ConstDeviceList FakeBluetoothAdapter::GetDevices() |
| 74 | const { |
| 75 | device::BluetoothAdapter::ConstDeviceList devices; |
| 76 | for (const auto& it : mock_devices_) { |
| 77 | devices.push_back(it.get()); |
| 78 | } |
| 79 | return devices; |
| 80 | } |
| 81 | |
| 82 | device::BluetoothDevice* FakeBluetoothAdapter::GetDevice( |
| 83 | const std::string& address) { |
| 84 | for (const auto& it : mock_devices_) { |
| 85 | if (it->GetAddress() == address) { |
| 86 | return it.get(); |
| 87 | } |
| 88 | } |
| 89 | return nullptr; |
| 90 | } |
| 91 | |
| 92 | void FakeBluetoothAdapter::StartScanWithFilter( |
| 93 | std::unique_ptr<device::BluetoothDiscoveryFilter> filter, |
| 94 | base::OnceCallback<void(/*is_error*/ bool, |
| 95 | device::UMABluetoothDiscoverySessionOutcome)> |
| 96 | callback) { |
| 97 | std::move(callback).Run( |
| 98 | /*is_error=*/false, device::UMABluetoothDiscoverySessionOutcome::SUCCESS); |
| 99 | } |
| 100 | |
| 101 | FakeBluetoothAdapter::~FakeBluetoothAdapter() = default; |
| 102 | |
| 103 | FakeBluetoothGattCharacteristic::FakeBluetoothGattCharacteristic( |
| 104 | MockBluetoothGattService* service, |
| 105 | const std::string& identifier, |
| 106 | const BluetoothUUID& uuid, |
| 107 | Properties properties, |
| 108 | Permissions permissions) |
| 109 | : testing::NiceMock<MockBluetoothGattCharacteristic>(service, |
| 110 | identifier, |
| 111 | uuid, |
| 112 | properties, |
| 113 | permissions), |
| 114 | value_({1}) {} |
| 115 | |
| 116 | FakeBluetoothGattCharacteristic::~FakeBluetoothGattCharacteristic() = default; |
| 117 | |
| 118 | void FakeBluetoothGattCharacteristic::ReadRemoteCharacteristic( |
| 119 | ValueCallback callback) { |
| 120 | if (!(GetProperties() & BluetoothGattCharacteristic::PROPERTY_READ)) { |
| 121 | std::move(callback).Run(BluetoothGattService::GattErrorCode::kNotPermitted, |
| 122 | std::vector<uint8_t>()); |
| 123 | return; |
| 124 | } |
| 125 | if (defer_read_until_notification_start_) { |
| 126 | DCHECK(!deferred_read_callback_); |
| 127 | deferred_read_callback_ = std::move(callback); |
| 128 | return; |
| 129 | } |
| 130 | std::move(callback).Run(/*error_code=*/std::nullopt, value_); |
| 131 | } |
| 132 | |
| 133 | void FakeBluetoothGattCharacteristic::StartNotifySession( |
| 134 | NotifySessionCallback callback, |
| 135 | ErrorCallback error_callback) { |
| 136 | if (!(GetProperties() & BluetoothGattCharacteristic::PROPERTY_NOTIFY)) { |
| 137 | std::move(error_callback) |
| 138 | .Run(BluetoothGattService::GattErrorCode::kNotPermitted); |
| 139 | return; |
| 140 | } |
| 141 | auto fake_notify_session = |
| 142 | std::make_unique<testing::NiceMock<MockBluetoothGattNotifySession>>( |
| 143 | GetWeakPtr()); |
| 144 | active_notify_sessions_.insert(fake_notify_session->unique_id()); |
| 145 | |
| 146 | if (deferred_read_callback_) { |
| 147 | // A new value as a result of calling readValue(). |
| 148 | std::move(deferred_read_callback_).Run(/*error_code=*/std::nullopt, value_); |
| 149 | } |
| 150 | |
| 151 | if (emit_value_change_at_notification_start_) { |
| 152 | BluetoothAdapter* adapter = GetService()->GetDevice()->GetAdapter(); |
| 153 | adapter->NotifyGattCharacteristicValueChanged(this, value_); |
| 154 | |
| 155 | // NotifyGattCharacteristicValueChanged(...) posts a task to notify the |
| 156 | // renderer of the change. Do the same for |callback| to ensure |
| 157 | // StartNotifySession completes after the value change notification is |
| 158 | // received. |
| 159 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| 160 | FROM_HERE, |
| 161 | base::BindOnce(std::move(callback), std::move(fake_notify_session))); |
| 162 | } else { |
| 163 | // Complete StartNotifySession normally. |
| 164 | std::move(callback).Run(std::move(fake_notify_session)); |
| 165 | } |
| 166 | EXPECT_TRUE(IsNotifying()); |
| 167 | } |
| 168 | |
| 169 | void FakeBluetoothGattCharacteristic::StopNotifySession( |
| 170 | BluetoothGattNotifySession::Id session, |
| 171 | base::OnceClosure callback) { |
| 172 | EXPECT_TRUE(base::Contains(active_notify_sessions_, session)); |
| 173 | std::move(callback).Run(); |
| 174 | } |
| 175 | |
| 176 | bool FakeBluetoothGattCharacteristic::IsNotifying() const { |
| 177 | return !active_notify_sessions_.empty(); |
| 178 | } |
| 179 | |
| 180 | // Do not call the readValue callback until midway through the completion |
| 181 | // of the startNotification callback registration. |
| 182 | // https://crbug.com/1153426 |
| 183 | void FakeBluetoothGattCharacteristic::DeferReadUntilNotificationStart() { |
| 184 | defer_read_until_notification_start_ = true; |
| 185 | } |
| 186 | |
| 187 | // Possibly trigger value characteristicvaluechanged events on the page |
| 188 | // during the setup of startNotifications. |
| 189 | // https://crbug.com/1153426. |
| 190 | void FakeBluetoothGattCharacteristic:: |
| 191 | EmitChangeNotificationAtNotificationStart() { |
| 192 | emit_value_change_at_notification_start_ = true; |
| 193 | } |
| 194 | |
| 195 | FakeBluetoothGattConnection::FakeBluetoothGattConnection( |
| 196 | scoped_refptr<device::BluetoothAdapter> adapter, |
| 197 | const std::string& device_address) |
| 198 | : testing::NiceMock<device::MockBluetoothGattConnection>(adapter, |
| 199 | device_address) {} |
| 200 | |
| 201 | FakeBluetoothDevice::FakeBluetoothDevice(device::MockBluetoothAdapter* adapter, |
| 202 | const std::string& address) |
| 203 | : testing::NiceMock<device::MockBluetoothDevice>(adapter, |
| 204 | /*bluetooth_class=*/0u, |
| 205 | /*name=*/"Test Device", |
| 206 | address, |
| 207 | /*paired=*/true, |
| 208 | /*connected=*/true) {} |
| 209 | |
| 210 | void FakeBluetoothDevice::CreateGattConnection( |
| 211 | device::BluetoothDevice::GattConnectionCallback callback, |
| 212 | std::optional<device::BluetoothUUID> service_uuid) { |
| 213 | SetConnected(true); |
| 214 | gatt_services_discovery_complete_ = true; |
| 215 | std::move(callback).Run( |
| 216 | std::make_unique<FakeBluetoothGattConnection>(adapter_, GetAddress()), |
| 217 | /*error_code=*/std::nullopt); |
| 218 | } |
| 219 | |
| 220 | bool FakeBluetoothDevice::IsGattServicesDiscoveryComplete() const { |
| 221 | return gatt_services_discovery_complete_; |
| 222 | } |
| 223 | |
| 224 | BluetoothRemoteGattService* FakeBluetoothDevice::GetGattService( |
| 225 | const std::string& identifier) const { |
| 226 | return GetMockService(identifier); |
| 227 | } |
| 228 | |
| 229 | std::vector<device::BluetoothRemoteGattService*> |
| 230 | FakeBluetoothDevice::GetGattServices() const { |
| 231 | return GetMockServices(); |
| 232 | } |
| 233 | |
| 234 | FakeBluetoothChooser::FakeBluetoothChooser( |
| 235 | content::BluetoothChooser::EventHandler event_handler, |
| 236 | const std::optional<std::string>& device_to_select) |
| 237 | : event_handler_(event_handler), device_to_select_(device_to_select) {} |
| 238 | |
| 239 | FakeBluetoothChooser::~FakeBluetoothChooser() = default; |
| 240 | |
| 241 | // content::BluetoothChooser implementation: |
| 242 | void FakeBluetoothChooser::AddOrUpdateDevice(const std::string& device_id, |
| 243 | bool should_update_name, |
| 244 | const std::u16string& device_name, |
| 245 | bool is_gatt_connected, |
| 246 | bool is_paired, |
| 247 | int signal_strength_level) { |
| 248 | // Select the first device that is added if |device_to_select_| is not |
| 249 | // populated. |
| 250 | if (!device_to_select_) { |
| 251 | event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id); |
| 252 | return; |
| 253 | } |
| 254 | |
| 255 | // Otherwise, select the added device if its device ID matches |
| 256 | // |device_to_select_|. |
| 257 | if (device_to_select_.value() == device_id) { |
| 258 | event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id); |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | TestBluetoothDelegate::TestBluetoothDelegate() |
| 263 | : ChromeBluetoothDelegate( |
| 264 | std::make_unique<ChromeBluetoothDelegateImplClient>()) {} |
| 265 | |
| 266 | TestBluetoothDelegate::~TestBluetoothDelegate() = default; |
| 267 | |
| 268 | void TestBluetoothDelegate::UseRealChooser() { |
| 269 | EXPECT_FALSE(device_to_select_.has_value()); |
| 270 | use_real_chooser_ = true; |
| 271 | } |
| 272 | |
| 273 | void TestBluetoothDelegate::SetDeviceToSelect( |
| 274 | const std::string& device_address) { |
| 275 | EXPECT_FALSE(use_real_chooser_); |
| 276 | device_to_select_ = device_address; |
| 277 | } |
| 278 | |
| 279 | std::unique_ptr<content::BluetoothChooser> |
| 280 | TestBluetoothDelegate::RunBluetoothChooser( |
| 281 | content::RenderFrameHost* frame, |
| 282 | const content::BluetoothChooser::EventHandler& event_handler) { |
| 283 | if (use_real_chooser_) { |
| 284 | return ChromeBluetoothDelegate::RunBluetoothChooser(frame, event_handler); |
| 285 | } |
| 286 | return std::make_unique<FakeBluetoothChooser>(event_handler, |
| 287 | device_to_select_); |
| 288 | } |
| 289 | |
| 290 | std::unique_ptr<content::BluetoothScanningPrompt> |
| 291 | TestBluetoothDelegate::ShowBluetoothScanningPrompt( |
| 292 | content::RenderFrameHost* frame, |
| 293 | const content::BluetoothScanningPrompt::EventHandler& event_handler) { |
| 294 | // Simulate that a prompt was accepted; no actual prompt is needed here. |
| 295 | event_handler.Run(content::BluetoothScanningPrompt::Event::kAllow); |
| 296 | return nullptr; |
| 297 | } |
| 298 | |
| 299 | BluetoothTestContentBrowserClient::BluetoothTestContentBrowserClient() = |
| 300 | default; |
| 301 | |
| 302 | BluetoothTestContentBrowserClient::~BluetoothTestContentBrowserClient() = |
| 303 | default; |
| 304 | |
| 305 | TestBluetoothDelegate* BluetoothTestContentBrowserClient::bluetooth_delegate() { |
| 306 | return &bluetooth_delegate_; |
| 307 | } |
| 308 | |
| 309 | content::BluetoothDelegate* |
| 310 | BluetoothTestContentBrowserClient::GetBluetoothDelegate() { |
| 311 | return &bluetooth_delegate_; |
| 312 | } |