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") {
sources = [
"test/fake_bluetooth.mojom",
]
public_deps = [
":interfaces",
]
use_once_callback = true
}
mojom("experimental_interfaces") {
......
......@@ -4,6 +4,8 @@
module bluetooth.mojom;
import "device/bluetooth/public/interfaces/uuid.mojom";
// FakeBluetooth and its related interfaces allow clients to control the global
// Bluetooth State as well as simulate Bluetooth events including finding new
// devices, simulating GATT attributes and its descendants, and simulating
......@@ -38,13 +40,15 @@ interface FakeBluetooth {
// See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
// LE Physical Transport".
interface FakeCentral {
// Simulates a peripheral with |address| and |name| that has already
// been connected to the system. If the peripheral existed already it
// updates its name.
// Simulates a peripheral with |address|, |name| and |known_service_uuids|
// that has already been connected to the system. If the peripheral existed
// already it updates its name and known UUIDs.
//
// 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 a peripheral through the system's settings. This method is
// 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 @@
#include <string>
#include <utility>
#include "device/bluetooth/bluetooth_device.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/test/fake_peripheral.h"
......@@ -21,6 +23,7 @@ FakeCentral::FakeCentral(mojom::CentralState state,
void FakeCentral::SimulatePreconnectedPeripheral(
const std::string& address,
const std::string& name,
const std::vector<device::BluetoothUUID>& known_service_uuids,
SimulatePreconnectedPeripheralCallback callback) {
auto device_iter = devices_.find(address);
if (device_iter == devices_.end()) {
......@@ -35,6 +38,8 @@ void FakeCentral::SimulatePreconnectedPeripheral(
static_cast<FakePeripheral*>(device_iter->second.get());
fake_peripheral->SetName(name);
fake_peripheral->SetGattConnected(true);
fake_peripheral->SetServiceUUIDs(device::BluetoothDevice::UUIDSet(
known_service_uuids.begin(), known_service_uuids.end()));
std::move(callback).Run();
}
......
......@@ -27,6 +27,7 @@ class FakeCentral : NON_EXPORTED_BASE(public mojom::FakeCentral),
void SimulatePreconnectedPeripheral(
const std::string& address,
const std::string& name,
const std::vector<device::BluetoothUUID>& known_service_uuids,
SimulatePreconnectedPeripheralCallback callback) override;
// BluetoothAdapter overrides:
......
......@@ -15,13 +15,17 @@ FakePeripheral::FakePeripheral(FakeCentral* fake_central,
FakePeripheral::~FakePeripheral() {}
void FakePeripheral::SetName(base::Optional<std::string> name) {
name_ = name;
name_ = std::move(name);
}
void FakePeripheral::SetGattConnected(bool connected) {
gatt_connected_ = connected;
}
void FakePeripheral::SetServiceUUIDs(UUIDSet service_uuids) {
service_uuids_ = std::move(service_uuids);
}
uint32_t FakePeripheral::GetBluetoothClass() const {
NOTREACHED();
return 0;
......@@ -102,7 +106,7 @@ bool FakePeripheral::IsConnecting() const {
}
device::BluetoothDevice::UUIDSet FakePeripheral::GetUUIDs() const {
return UUIDSet();
return service_uuids_;
}
bool FakePeripheral::ExpectingPinCode() const {
......
......@@ -27,6 +27,10 @@ class FakePeripheral : public device::BluetoothDevice {
// Set it to indicate if the Peripheral is connected or not.
void SetGattConnected(bool gatt_connected);
// Updates the peripheral's UUIDs that are returned by
// BluetoothDevice::GetUUIDs().
void SetServiceUUIDs(UUIDSet service_uuids);
// BluetoothDevice overrides:
uint32_t GetBluetoothClass() const override;
#if defined(OS_CHROMEOS) || defined(OS_LINUX)
......@@ -80,6 +84,7 @@ class FakePeripheral : public device::BluetoothDevice {
const std::string address_;
base::Optional<std::string> name_;
bool gatt_connected_;
UUIDSet service_uuids_;
DISALLOW_COPY_AND_ASSIGN(FakePeripheral);
};
......
......@@ -2,6 +2,8 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
test(() => {
assert_throws(null, () => new BluetoothDevice(),
......@@ -11,7 +13,7 @@ test(() => {
}, 'BluetoothDevice IDL test');
promise_test(() => {
return setBluetoothFakeAdapter('GlucoseHeartRateAdapter')
return setUpHealthThermometerAndHeartRateDevices()
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['heart_rate']}]}))
.then(device => {
......@@ -23,7 +25,7 @@ promise_test(() => {
device.id = 'overwritten';
device.name = 'overwritten';
assert_equals(device.id, old_device_id);
assert_equals(device.name, 'Heart Rate Device');
assert_equals(device.name, 'Heart Rate');
});
}, 'BluetoothDevice attributes.');
</script>
......@@ -2,10 +2,13 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('BlocklistTestAdapter')
return setUpPreconnectedDevice({
knownServiceUUIDs: ['human_interface_device']})
.then(() => assert_promise_rejects_with_message(
requestDeviceWithKeyDown({
filters: [{services: ['human_interface_device']}]}),
......
......@@ -2,10 +2,12 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('HeartRateAdapter')
return setUpHealthThermometerAndHeartRateDevices()
.then(() => callWithKeyDown(() => {
var first = navigator.bluetooth.requestDevice({
filters: [{services: ['heart_rate']}]});
......
......@@ -2,10 +2,12 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
'use strict';
promise_test(t => {
return setBluetoothFakeAdapter('HeartRateAdapter')
return setUpHealthThermometerAndHeartRateDevices()
.then(() => promise_rejects(
t, 'SecurityError', navigator.bluetooth.requestDevice({
filters: [{services: ['heart_rate']}]})));
......
......@@ -2,6 +2,8 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
<script>
"use strict";
......@@ -36,7 +38,7 @@
});
});
return setBluetoothFakeAdapter('HeartRateAdapter')
return setUpHealthThermometerAndHeartRateDevices()
.then(() => {
for (let i = 0; i < numIframes; i++) {
let iframe = document.createElement('iframe');
......
......@@ -2,13 +2,15 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
'use strict';
promise_test(() => {
let devices = [];
let push = device => devices.push(device);
return setBluetoothFakeAdapter('HeartRateAdapter')
return setUpHealthThermometerAndHeartRateDevices()
.then(() => requestDeviceWithKeyDown({
filters: [{services: [heart_rate.alias]}]}))
.then(push)
......
......@@ -2,12 +2,14 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.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>
'use strict';
promise_test(() => {
return setBluetoothFakeAdapter('GlucoseHeartRateAdapter')
return setUpHealthThermometerAndHeartRateDevices()
.then(() => requestDeviceWithKeyDown({
filters: [{services: ['glucose']}]}))
.then(device => assert_equals(device.name, 'Glucose Device'));
filters: [{services: ['health_thermometer']}]}))
.then(device => assert_equals(device.name, 'Health Thermometer'));
}, 'Simple filter selects matching device.');
</script>
......@@ -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'})
.then(fake_central => fake_central.simulatePreconnectedPeripheral({
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 @@
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 {
constructor() {
this.fake_bluetooth_ptr_ = undefined;
......@@ -45,9 +58,10 @@
// TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no
// longer used.
await setBluetoothFakeAdapter('');
await this.initFakeBluetoothInterfacePtr_();
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
......@@ -71,33 +85,17 @@
// TODO(crbug.com/569709): Remove once setBluetoothFakeAdapter is no
// longer used.
await setBluetoothFakeAdapter('');
await this.initFakeBluetoothInterfacePtr_();
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} =
await (await this.getFakeBluetoothInterface_()).simulateCentral(
mojo_manager_state);
await this.fake_bluetooth_ptr_.simulateCentral(
toMojoCentralState(state));
return new FakeCentral(fake_central_ptr);
}
async getFakeBluetoothInterface_() {
async initFakeBluetoothInterfacePtr_() {
if (typeof this.fake_bluetooth_ptr_ !== 'undefined') {
return this.fake_bluetooth_ptr_;
}
......@@ -106,8 +104,6 @@
this.fake_bluetooth_ptr_ = new mojo.FakeBluetoothPtr(
mojo.interfaces.getInterface(mojo.FakeBluetooth.name));
return this.fake_bluetooth_ptr_;
}
}
......@@ -120,17 +116,26 @@
this.peripherals_ = new Map();
}
// Simulates a peripheral with |address| and |name| that has already
// been connected to the system. If the peripheral existed already it
// updates its name.
// Simulates a peripheral with |address|, |name| and |known_service_uuids|
// that has already been connected to the system. If the peripheral existed
// 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
// 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 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
// 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(
address, name);
address, name, knownServiceUUIDs);
let peripheral = this.peripherals_.get(address);
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