0

bluetooth: Update Bluetooth Device Emulation APIs in CDP

This CL updates Bluetooth Device Emulation APIs in CDP:
- Adding a new parameter of leSupported to BluetoothEmulation.enable for configuring low-energy support.
- Adding a new API to update the simulated central state.

Bug: 398026399
Change-Id: I212072f63c337a0347e54b071c8f99d55b6d6f95
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6305026
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Jack Hsieh <chengweih@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1429408}
This commit is contained in:
Chengwei Hsieh
2025-03-07 02:26:35 -08:00
committed by Chromium LUCI CQ
parent 4940cf0dd4
commit 5dd2f541b9
22 changed files with 137 additions and 17 deletions

@ -94,16 +94,23 @@ void BluetoothEmulationHandler::Wire(UberDispatcher* dispatcher) {
BluetoothEmulation::Dispatcher::wire(dispatcher, this);
}
Response BluetoothEmulationHandler::Enable(const String& in_state) {
if (emulation_enabled_ || fake_central_.is_bound()) {
Response BluetoothEmulationHandler::Enable(const String& in_state,
bool in_le_supported) {
if (emulation_enabled_) {
return Response::ServerError("BluetoothEmulation already enabled");
}
CHECK(!fake_central_.is_bound());
emulation_enabled_ = true;
content::BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(
base::MakeRefCounted<bluetooth::FakeCentral>(
ToCentralState(in_state),
fake_central_.BindNewPipeAndPassReceiver()));
global_factory_values_ =
BluetoothAdapterFactory::Get()->InitGlobalOverrideValues();
global_factory_values_->SetLESupported(in_le_supported);
if (in_le_supported) {
content::BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(
base::MakeRefCounted<bluetooth::FakeCentral>(
ToCentralState(in_state),
fake_central_.BindNewPipeAndPassReceiver()));
}
return Response::Success();
}
@ -114,11 +121,31 @@ Response BluetoothEmulationHandler::Disable() {
// reset this only if this is the instance holding the bound central.
content::BluetoothAdapterFactoryWrapper::Get().SetBluetoothAdapterOverride(
nullptr);
}
// The instance that has a valid `global_factory_values_` is the one that sets
// `emulation_enabled_`. Therefore, only that instance can reset
// `emulation_enabled_`.
if (global_factory_values_) {
global_factory_values_.reset();
emulation_enabled_ = false;
}
return Response::Success();
}
void BluetoothEmulationHandler::SetSimulatedCentralState(
const String& in_state,
std::unique_ptr<SetSimulatedCentralStateCallback> callback) {
if (!fake_central_.is_bound()) {
std::move(callback)->sendFailure(
Response::ServerError("BluetoothEmulation not enabled"));
return;
}
fake_central_->SetState(
ToCentralState(in_state),
base::BindOnce(&SetSimulatedCentralStateCallback::sendSuccess,
std::move(callback)));
}
void BluetoothEmulationHandler::SimulatePreconnectedPeripheral(
const String& in_address,
const String& in_name,

@ -29,8 +29,11 @@ class CONTENT_EXPORT BluetoothEmulationHandler
private:
// BluetoothEmulation::Backend
Response Enable(const String& in_state) override;
Response Enable(const String& in_state, bool in_le_supported) override;
Response Disable() override;
void SetSimulatedCentralState(
const String& in_state,
std::unique_ptr<SetSimulatedCentralStateCallback> callback) override;
void SimulatePreconnectedPeripheral(
const String& in_address,
const String& in_name,
@ -52,6 +55,8 @@ class CONTENT_EXPORT BluetoothEmulationHandler
// support one fake adapter at a time.
inline static bool emulation_enabled_ = false;
mojo::Remote<bluetooth::mojom::FakeCentral> fake_central_;
std::unique_ptr<device::BluetoothAdapterFactory::GlobalOverrideValues>
global_factory_values_;
};
} // namespace content::protocol

@ -176,6 +176,7 @@
{
"domain": "BluetoothEmulation",
"async": [
"setSimulatedCentralState",
"simulatePreconnectedPeripheral",
"simulateAdvertisement"
]

@ -28,7 +28,8 @@
await pProtocol.Page.navigate({url: 'https://test.com/index.html'});
// Simulate an adapter and a bluetooth device.
await bProtocol.BluetoothEmulation.enable({state: 'powered-on'});
await bProtocol.BluetoothEmulation.enable(
{state: 'powered-on', leSupported: true});
await bProtocol.BluetoothEmulation.simulatePreconnectedPeripheral({
address: '09:09:09:09:09:09',
name: 'Test BLE device',

@ -13152,6 +13152,14 @@ experimental domain BluetoothEmulation
# Enable the BluetoothEmulation domain.
command enable
parameters
# State of the simulated central.
CentralState state
# If the simulated central supports low-energy.
boolean leSupported
# Set the state of the simulated central.
command setSimulatedCentralState
parameters
# State of the simulated central.
CentralState state

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] getAvailability() resolves with false if the system does not have an adapter.

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] getAvailability() resolves with true if the Bluetooth radio is powered off, but the platform that supports Bluetooth LE.

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] getAvailability() resolves with true if the Bluetooth radio is powered on and the platform supports Bluetooth LE.

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] getAvailability() resolves with false if called from a unique origin

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] Device with empty name and no UUIDs nearby. Should be found if acceptAllDevices is true.

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] A device with name and no UUIDs nearby. Should be found if acceptAllDevices is true.

@ -0,0 +1,2 @@
This is a testharness.js-based test.
[FAIL] Reject with NotFoundError if there is no BT radio present.

@ -3,9 +3,9 @@
'Tests that Bluetooth is unavailable when simulateCentral is set to absent');
const bp = testRunner.browserP();
await bp.BluetoothEmulation.enable({state: 'absent'});
await bp.BluetoothEmulation.enable({state: 'absent', leSupported: true});
testRunner.log(await session.evaluateAsync(
() => navigator.bluetooth.getAvailability()), undefined, 'Bluetooth availability');
testRunner.completeTest();
});
});

@ -3,10 +3,12 @@
'Tests that repeated Bluetooth.simulateCentral calls are avoided');
const bp = testRunner.browserP();
const first = await bp.BluetoothEmulation.enable({state: 'powered-on'});
const first = await bp.BluetoothEmulation.enable(
{state: 'powered-on', leSupported: true});
testRunner.log(first);
const second = await bp.BluetoothEmulation.enable({state: 'powered-on'});
const second = await bp.BluetoothEmulation.enable(
{state: 'powered-on', leSupported: true});
testRunner.log(second);
testRunner.completeTest();
});
});

@ -3,10 +3,10 @@
'Tests that Bluetooth.simulateCentral sets Bluetooth availability');
const bp = testRunner.browserP();
await bp.BluetoothEmulation.enable({state: 'powered-on'});
await bp.BluetoothEmulation.enable({state: 'powered-on', leSupported: true});
testRunner.log(await session.evaluateAsync(
() => navigator.bluetooth.getAvailability()
));
testRunner.completeTest();
});
});

@ -0,0 +1,3 @@
Tests that Bluetooth is unavailable when low-energy is not supported.
false

@ -0,0 +1,11 @@
(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
const {session} = await testRunner.startBlank(
'Tests that Bluetooth is unavailable when low-energy is not supported.');
const bp = testRunner.browserP();
await bp.BluetoothEmulation.enable({state: 'powered-on', leSupported: false});
testRunner.log(
await session.evaluateAsync(() => navigator.bluetooth.getAvailability()));
testRunner.completeTest();
});

@ -0,0 +1,4 @@
Tests that Bluetooth is available when simulateCentral is re-enabled.
true
true

@ -0,0 +1,17 @@
(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
const {session} = await testRunner.startBlank(
'Tests that Bluetooth is available when simulateCentral is re-enabled.');
const bp = testRunner.browserP();
await bp.BluetoothEmulation.enable({state: 'powered-on', leSupported: true});
testRunner.log(
await session.evaluateAsync(() => navigator.bluetooth.getAvailability()),
'Bluetooth availability');
await bp.BluetoothEmulation.disable();
await bp.BluetoothEmulation.enable({state: 'powered-on', leSupported: true});
testRunner.log(
await session.evaluateAsync(() => navigator.bluetooth.getAvailability()),
'Bluetooth availability');
testRunner.completeTest();
});

@ -0,0 +1,5 @@
Tests updating the state of the simulateCentral.
true
true
false

@ -0,0 +1,22 @@
(async function(/** @type {import('test_runner').TestRunner} */ testRunner) {
const {session} = await testRunner.startBlank(
'Tests updating the state of the simulateCentral.');
const bp = testRunner.browserP();
await bp.BluetoothEmulation.enable({state: 'powered-on', leSupported: true});
testRunner.log(
await session.evaluateAsync(() => navigator.bluetooth.getAvailability()),
'Bluetooth availability when the adapter is powered-on');
await bp.BluetoothEmulation.setSimulatedCentralState(
{state: 'powered-off', leSupported: true});
testRunner.log(await session.evaluateAsync(
() => navigator.bluetooth.getAvailability(),
'Bluetooth availability when the adapter is powered-off'));
await bp.BluetoothEmulation.setSimulatedCentralState(
{state: 'absent', leSupported: true});
testRunner.log(await session.evaluateAsync(
() => navigator.bluetooth.getAvailability(),
'Bluetooth availability when the adapter is absent'));
testRunner.completeTest();
});

@ -5,7 +5,7 @@
await dp.Page.enable();
await dp.Runtime.enable();
await bp.BluetoothEmulation.enable({state: 'powered-on'});
await bp.BluetoothEmulation.enable({state: 'powered-on', leSupported: true});
const bluetoothAvailable = await session.evaluateAsync(
() => navigator.bluetooth.getAvailability()
);
@ -56,4 +56,4 @@
testRunner.log(results);
testRunner.completeTest();
});
});