Commit 46a4de44 authored by ortuno's avatar ortuno Committed by Commit bot

bluetooth: Implement known_service_uuids for Peripherals.

Platforms expose methods to retrieve devices that have connected outside the
user agent and their UUIDs:

Android: BluetoothManager#getConnectedDevices[1] and getUuids()[2]
macOS: CBCentralManager:retrieveConnectedPeripheralsWithServices:[3]
Windows: DeviceInformation::FindAllAsync[4] with GattDeviceService::GetDeviceSelectorFromUuid()[5]

Allows tests to provide known_service_uuids that would cause the returned
devices from the platform functions to change.

[1] https://developer.android.com/reference/android/bluetooth/BluetoothManager.html#getConnectedDevices(int)
[2] https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getUuids()
[3] https://developer.apple.com/reference/corebluetooth/cbcentralmanager/1518924-retrieveconnectedperipheralswith?language=objc
[4] https://docs.microsoft.com/en-us/uwp/api/windows.devices.enumeration.deviceinformation#Windows_Devices_Enumeration_DeviceInformation_FindAllAsync_System_String_
[5] https://docs.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.genericattributeprofile.gattdeviceservice#Windows_Devices_Bluetooth_GenericAttributeProfile_GattDeviceService_GetDeviceSelectorFromUuid_System_Guid_

BUG=719813

Review-Url: https://codereview.chromium.org/2867713008
Cr-Commit-Position: refs/heads/master@{#471197}
parent 8628a9e9
...@@ -14,6 +14,12 @@ mojom("fake_bluetooth_interfaces") { ...@@ -14,6 +14,12 @@ mojom("fake_bluetooth_interfaces") {
sources = [ sources = [
"test/fake_bluetooth.mojom", "test/fake_bluetooth.mojom",
] ]
public_deps = [
":interfaces",
]
use_once_callback = true
} }
mojom("experimental_interfaces") { mojom("experimental_interfaces") {
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
module bluetooth.mojom; module bluetooth.mojom;
import "device/bluetooth/public/interfaces/uuid.mojom";
// FakeBluetooth and its related interfaces allow clients to control the global // FakeBluetooth and its related interfaces allow clients to control the global
// Bluetooth State as well as simulate Bluetooth events including finding new // Bluetooth State as well as simulate Bluetooth events including finding new
// devices, simulating GATT attributes and its descendants, and simulating // devices, simulating GATT attributes and its descendants, and simulating
...@@ -38,13 +40,15 @@ interface FakeBluetooth { ...@@ -38,13 +40,15 @@ interface FakeBluetooth {
// See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an // See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
// LE Physical Transport". // LE Physical Transport".
interface FakeCentral { interface FakeCentral {
// Simulates a peripheral with |address| and |name| that has already // Simulates a peripheral with |address|, |name| and |known_service_uuids|
// been connected to the system. If the peripheral existed already it // that has already been connected to the system. If the peripheral existed
// updates its name. // already it updates its name and known UUIDs.
// //
// Platforms offer methods to retrieve devices that have already been // Platforms offer methods to retrieve devices that have already been
// connected to the system or weren't connected through the UA e.g. a user // connected to the system or weren't connected through the UA e.g. a user
// connected a peripheral through the system's settings. This method is // connected a peripheral through the system's settings. This method is
// intended to simulate peripherals that those methods would return. // intended to simulate peripherals that those methods would return.
SimulatePreconnectedPeripheral(string address, string name) => (); SimulatePreconnectedPeripheral(string address,
string name,
array<UUID> known_service_uuids) => ();
}; };
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_discovery_filter.h" #include "device/bluetooth/bluetooth_discovery_filter.h"
#include "device/bluetooth/bluetooth_uuid.h"
#include "device/bluetooth/public/interfaces/test/fake_bluetooth.mojom.h" #include "device/bluetooth/public/interfaces/test/fake_bluetooth.mojom.h"
#include "device/bluetooth/test/fake_peripheral.h" #include "device/bluetooth/test/fake_peripheral.h"
...@@ -21,6 +23,7 @@ FakeCentral::FakeCentral(mojom::CentralState state, ...@@ -21,6 +23,7 @@ FakeCentral::FakeCentral(mojom::CentralState state,
void FakeCentral::SimulatePreconnectedPeripheral( void FakeCentral::SimulatePreconnectedPeripheral(
const std::string& address, const std::string& address,
const std::string& name, const std::string& name,
const std::vector<device::BluetoothUUID>& known_service_uuids,
SimulatePreconnectedPeripheralCallback callback) { SimulatePreconnectedPeripheralCallback callback) {
auto device_iter = devices_.find(address); auto device_iter = devices_.find(address);
if (device_iter == devices_.end()) { if (device_iter == devices_.end()) {
...@@ -35,6 +38,8 @@ void FakeCentral::SimulatePreconnectedPeripheral( ...@@ -35,6 +38,8 @@ void FakeCentral::SimulatePreconnectedPeripheral(
static_cast<FakePeripheral*>(device_iter->second.get()); static_cast<FakePeripheral*>(device_iter->second.get());
fake_peripheral->SetName(name); fake_peripheral->SetName(name);
fake_peripheral->SetGattConnected(true); fake_peripheral->SetGattConnected(true);
fake_peripheral->SetServiceUUIDs(device::BluetoothDevice::UUIDSet(
known_service_uuids.begin(), known_service_uuids.end()));
std::move(callback).Run(); std::move(callback).Run();
} }
......
...@@ -27,6 +27,7 @@ class FakeCentral : NON_EXPORTED_BASE(public mojom::FakeCentral), ...@@ -27,6 +27,7 @@ class FakeCentral : NON_EXPORTED_BASE(public mojom::FakeCentral),
void SimulatePreconnectedPeripheral( void SimulatePreconnectedPeripheral(
const std::string& address, const std::string& address,
const std::string& name, const std::string& name,
const std::vector<device::BluetoothUUID>& known_service_uuids,
SimulatePreconnectedPeripheralCallback callback) override; SimulatePreconnectedPeripheralCallback callback) override;
// BluetoothAdapter overrides: // BluetoothAdapter overrides:
......
...@@ -15,13 +15,17 @@ FakePeripheral::FakePeripheral(FakeCentral* fake_central, ...@@ -15,13 +15,17 @@ FakePeripheral::FakePeripheral(FakeCentral* fake_central,
FakePeripheral::~FakePeripheral() {} FakePeripheral::~FakePeripheral() {}
void FakePeripheral::SetName(base::Optional<std::string> name) { void FakePeripheral::SetName(base::Optional<std::string> name) {
name_ = name; name_ = std::move(name);
} }
void FakePeripheral::SetGattConnected(bool connected) { void FakePeripheral::SetGattConnected(bool connected) {
gatt_connected_ = connected; gatt_connected_ = connected;
} }
void FakePeripheral::SetServiceUUIDs(UUIDSet service_uuids) {
service_uuids_ = std::move(service_uuids);
}
uint32_t FakePeripheral::GetBluetoothClass() const { uint32_t FakePeripheral::GetBluetoothClass() const {
NOTREACHED(); NOTREACHED();
return 0; return 0;
...@@ -102,7 +106,7 @@ bool FakePeripheral::IsConnecting() const { ...@@ -102,7 +106,7 @@ bool FakePeripheral::IsConnecting() const {
} }
device::BluetoothDevice::UUIDSet FakePeripheral::GetUUIDs() const { device::BluetoothDevice::UUIDSet FakePeripheral::GetUUIDs() const {
return UUIDSet(); return service_uuids_;
} }
bool FakePeripheral::ExpectingPinCode() const { bool FakePeripheral::ExpectingPinCode() const {
......
...@@ -27,6 +27,10 @@ class FakePeripheral : public device::BluetoothDevice { ...@@ -27,6 +27,10 @@ class FakePeripheral : public device::BluetoothDevice {
// Set it to indicate if the Peripheral is connected or not. // Set it to indicate if the Peripheral is connected or not.
void SetGattConnected(bool gatt_connected); void SetGattConnected(bool gatt_connected);
// Updates the peripheral's UUIDs that are returned by
// BluetoothDevice::GetUUIDs().
void SetServiceUUIDs(UUIDSet service_uuids);
// BluetoothDevice overrides: // BluetoothDevice overrides:
uint32_t GetBluetoothClass() const override; uint32_t GetBluetoothClass() const override;
#if defined(OS_CHROMEOS) || defined(OS_LINUX) #if defined(OS_CHROMEOS) || defined(OS_LINUX)
...@@ -80,6 +84,7 @@ class FakePeripheral : public device::BluetoothDevice { ...@@ -80,6 +84,7 @@ class FakePeripheral : public device::BluetoothDevice {
const std::string address_; const std::string address_;
base::Optional<std::string> name_; base::Optional<std::string> name_;
bool gatt_connected_; bool gatt_connected_;
UUIDSet service_uuids_;
DISALLOW_COPY_AND_ASSIGN(FakePeripheral); DISALLOW_COPY_AND_ASSIGN(FakePeripheral);
}; };
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<script> <script>
test(() => { test(() => {
assert_throws(null, () => new BluetoothDevice(), assert_throws(null, () => new BluetoothDevice(),
...@@ -11,7 +13,7 @@ test(() => { ...@@ -11,7 +13,7 @@ test(() => {
}, 'BluetoothDevice IDL test'); }, 'BluetoothDevice IDL test');
promise_test(() => { promise_test(() => {
return setBluetoothFakeAdapter('GlucoseHeartRateAdapter') return setUpHealthThermometerAndHeartRateDevices()
.then(() => requestDeviceWithKeyDown({ .then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]})) filters: [{services: ['heart_rate']}]}))
.then(device => { .then(device => {
...@@ -23,7 +25,7 @@ promise_test(() => { ...@@ -23,7 +25,7 @@ promise_test(() => {
device.id = 'overwritten'; device.id = 'overwritten';
device.name = 'overwritten'; device.name = 'overwritten';
assert_equals(device.id, old_device_id); assert_equals(device.id, old_device_id);
assert_equals(device.name, 'Heart Rate Device'); assert_equals(device.name, 'Heart Rate');
}); });
}, 'BluetoothDevice attributes.'); }, 'BluetoothDevice attributes.');
</script> </script>
...@@ -2,10 +2,13 @@ ...@@ -2,10 +2,13 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<script> <script>
'use strict'; 'use strict';
promise_test(() => { promise_test(() => {
return setBluetoothFakeAdapter('BlocklistTestAdapter') return setUpPreconnectedDevice({
knownServiceUUIDs: ['human_interface_device']})
.then(() => assert_promise_rejects_with_message( .then(() => assert_promise_rejects_with_message(
requestDeviceWithKeyDown({ requestDeviceWithKeyDown({
filters: [{services: ['human_interface_device']}]}), filters: [{services: ['human_interface_device']}]}),
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<script> <script>
'use strict'; 'use strict';
promise_test(t => { promise_test(t => {
return setBluetoothFakeAdapter('HeartRateAdapter') return setUpHealthThermometerAndHeartRateDevices()
.then(() => callWithKeyDown(() => { .then(() => callWithKeyDown(() => {
var first = navigator.bluetooth.requestDevice({ var first = navigator.bluetooth.requestDevice({
filters: [{services: ['heart_rate']}]}); filters: [{services: ['heart_rate']}]});
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<script> <script>
'use strict'; 'use strict';
promise_test(t => { promise_test(t => {
return setBluetoothFakeAdapter('HeartRateAdapter') return setUpHealthThermometerAndHeartRateDevices()
.then(() => promise_rejects( .then(() => promise_rejects(
t, 'SecurityError', navigator.bluetooth.requestDevice({ t, 'SecurityError', navigator.bluetooth.requestDevice({
filters: [{services: ['heart_rate']}]}))); filters: [{services: ['heart_rate']}]})));
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<body> <body>
<script> <script>
"use strict"; "use strict";
...@@ -36,7 +38,7 @@ ...@@ -36,7 +38,7 @@
}); });
}); });
return setBluetoothFakeAdapter('HeartRateAdapter') return setUpHealthThermometerAndHeartRateDevices()
.then(() => { .then(() => {
for (let i = 0; i < numIframes; i++) { for (let i = 0; i < numIframes; i++) {
let iframe = document.createElement('iframe'); let iframe = document.createElement('iframe');
......
...@@ -2,13 +2,15 @@ ...@@ -2,13 +2,15 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<script> <script>
'use strict'; 'use strict';
promise_test(() => { promise_test(() => {
let devices = []; let devices = [];
let push = device => devices.push(device); let push = device => devices.push(device);
return setBluetoothFakeAdapter('HeartRateAdapter') return setUpHealthThermometerAndHeartRateDevices()
.then(() => requestDeviceWithKeyDown({ .then(() => requestDeviceWithKeyDown({
filters: [{services: [heart_rate.alias]}]})) filters: [{services: [heart_rate.alias]}]}))
.then(push) .then(push)
......
...@@ -2,12 +2,14 @@ ...@@ -2,12 +2,14 @@
<script src="../../resources/testharness.js"></script> <script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script> <script src="../../resources/testharnessreport.js"></script>
<script src="../../resources/bluetooth/bluetooth-helpers.js"></script> <script src="../../resources/bluetooth/bluetooth-helpers.js"></script>
<script src="../../resources/bluetooth/web-bluetooth-test.js"></script>
<script src="../../resources/mojo-helpers.js"></script>
<script> <script>
'use strict'; 'use strict';
promise_test(() => { promise_test(() => {
return setBluetoothFakeAdapter('GlucoseHeartRateAdapter') return setUpHealthThermometerAndHeartRateDevices()
.then(() => requestDeviceWithKeyDown({ .then(() => requestDeviceWithKeyDown({
filters: [{services: ['glucose']}]})) filters: [{services: ['health_thermometer']}]}))
.then(device => assert_equals(device.name, 'Glucose Device')); .then(device => assert_equals(device.name, 'Health Thermometer'));
}, 'Simple filter selects matching device.'); }, 'Simple filter selects matching device.');
</script> </script>
...@@ -423,10 +423,27 @@ function generateRequestDeviceArgsWithServices(services = ['heart_rate']) { ...@@ -423,10 +423,27 @@ function generateRequestDeviceArgsWithServices(services = ['heart_rate']) {
}]; }];
} }
function setUpPreconnectedDevice({address = '00:00:00:00:00:00', name}) { function setUpPreconnectedDevice({
address = '00:00:00:00:00:00', name = 'LE Device', knownServiceUUIDs = []}) {
return navigator.bluetooth.test.simulateCentral({state: 'powered-on'}) return navigator.bluetooth.test.simulateCentral({state: 'powered-on'})
.then(fake_central => fake_central.simulatePreconnectedPeripheral({ .then(fake_central => fake_central.simulatePreconnectedPeripheral({
address: address, address: address,
name: name name: name,
knownServiceUUIDs: knownServiceUUIDs,
})); }));
} }
function setUpHealthThermometerAndHeartRateDevices() {
return navigator.bluetooth.test.simulateCentral({state: 'powered-on'})
.then(fake_central => Promise.all([
fake_central.simulatePreconnectedPeripheral({
address: '09:09:09:09:09:09',
name: 'Health Thermometer',
knownServiceUUIDs: ['generic_access', 'health_thermometer'],
}),
fake_central.simulatePreconnectedPeripheral({
address: '08:08:08:08:08:08',
name: 'Heart Rate',
knownServiceUUIDs: ['generic_access', 'heart_rate'],
})]));
}
...@@ -26,6 +26,19 @@ ...@@ -26,6 +26,19 @@
return mojo_; return mojo_;
} }
function toMojoCentralState(state) {
switch (state) {
case 'absent':
return mojo_.CentralState.ABSENT;
case 'powered-off':
return mojo_.CentralState.POWERED_OFF;
case 'powered-on':
return mojo_.CentralState.POWERED_ON;
default:
throw `Unsupported value ${state} for state.`;
}
}
class FakeBluetooth { class FakeBluetooth {
constructor() { constructor() {
this.fake_bluetooth_ptr_ = undefined; this.fake_bluetooth_ptr_ = undefined;
...@@ -45,9 +58,10 @@ ...@@ -45,9 +58,10 @@
// TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no // TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no
// longer used. // longer used.
await setBluetoothFakeAdapter(''); await setBluetoothFakeAdapter('');
await this.initFakeBluetoothInterfacePtr_();
if (typeof supported !== 'boolean') throw 'Type Not Supported'; if (typeof supported !== 'boolean') throw 'Type Not Supported';
await (await this.getFakeBluetoothInterface_()).setLESupported(supported); await this.fake_bluetooth_ptr_.setLESupported(supported);
} }
// Returns a promise that resolves with a FakeCentral that clients can use // Returns a promise that resolves with a FakeCentral that clients can use
...@@ -71,33 +85,17 @@ ...@@ -71,33 +85,17 @@
// TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no // TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no
// longer used. // longer used.
await setBluetoothFakeAdapter(''); await setBluetoothFakeAdapter('');
await this.initFakeBluetoothInterfacePtr_();
await this.setLESupported(true); await this.setLESupported(true);
let mojo = await loadFakeBluetoothInterfaces();
let mojo_manager_state;
switch (state) {
case 'absent':
mojo_manager_state = mojo.CentralState.ABSENT;
break;
case 'powered-off':
mojo_manager_state = mojo.CentralState.POWERED_OFF;
break;
case 'powered-on':
mojo_manager_state = mojo.CentralState.POWERED_ON;
break;
default:
throw `Unsupported value ${state} for state.`;
}
let {fake_central:fake_central_ptr} = let {fake_central:fake_central_ptr} =
await (await this.getFakeBluetoothInterface_()).simulateCentral( await this.fake_bluetooth_ptr_.simulateCentral(
mojo_manager_state); toMojoCentralState(state));
return new FakeCentral(fake_central_ptr); return new FakeCentral(fake_central_ptr);
} }
async getFakeBluetoothInterface_() { async initFakeBluetoothInterfacePtr_() {
if (typeof this.fake_bluetooth_ptr_ !== 'undefined') { if (typeof this.fake_bluetooth_ptr_ !== 'undefined') {
return this.fake_bluetooth_ptr_; return this.fake_bluetooth_ptr_;
} }
...@@ -106,8 +104,6 @@ ...@@ -106,8 +104,6 @@
this.fake_bluetooth_ptr_ = new mojo.FakeBluetoothPtr( this.fake_bluetooth_ptr_ = new mojo.FakeBluetoothPtr(
mojo.interfaces.getInterface(mojo.FakeBluetooth.name)); mojo.interfaces.getInterface(mojo.FakeBluetooth.name));
return this.fake_bluetooth_ptr_;
} }
} }
...@@ -120,17 +116,26 @@ ...@@ -120,17 +116,26 @@
this.peripherals_ = new Map(); this.peripherals_ = new Map();
} }
// Simulates a peripheral with |address| and |name| that has already // Simulates a peripheral with |address|, |name| and |known_service_uuids|
// been connected to the system. If the peripheral existed already it // that has already been connected to the system. If the peripheral existed
// updates its name. // already it updates its name and known UUIDs. |known_service_uuids| should
// be an array of BluetoothServiceUUIDs
// https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
// //
// Platforms offer methods to retrieve devices that have already been // Platforms offer methods to retrieve devices that have already been
// connected to the system or weren't connected through the UA e.g. a // connected to the system or weren't connected through the UA e.g. a user
// user connected a peripheral through the system's settings. This method is // connected a peripheral through the system's settings. This method is
// intended to simulate peripherals that those methods would return. // intended to simulate peripherals that those methods would return.
async simulatePreconnectedPeripheral({address, name}) { async simulatePreconnectedPeripheral({
address, name, knownServiceUUIDs = []}) {
// Canonicalize and convert to mojo UUIDs.
knownServiceUUIDs.forEach((val, i, arr) => {
knownServiceUUIDs[i] = {uuid: BluetoothUUID.getService(val)};
});
await this.fake_central_ptr_.simulatePreconnectedPeripheral( await this.fake_central_ptr_.simulatePreconnectedPeripheral(
address, name); address, name, knownServiceUUIDs);
let peripheral = this.peripherals_.get(address); let peripheral = this.peripherals_.get(address);
if (peripheral === undefined) { if (peripheral === undefined) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment