Commit 97254d4f authored by Vicky Min's avatar Vicky Min Committed by Commit Bot

[bluetooth] Enumeration of serial devices

For enumeration of serial devices, it is necessary to register
an observer using an adapter instance. With the adapter it is
also possible to use GetDevices() to get a list of the current
discovered devices which should allow serial devices to show up
in the serial device chooser.

Bug: 1043300
Change-Id: I2bc0263f0f2baaacbdc86b6d97034017dff61412
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2291316Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarOvidio de Jesús Ruiz-Henríquez <odejesush@chromium.org>
Reviewed-by: default avatarMatt Reynolds <mattreynolds@chromium.org>
Reviewed-by: default avatarMartin Barbella <mbarbella@chromium.org>
Commit-Queue: Vicky Min <vickymin@google.com>
Cr-Commit-Position: refs/heads/master@{#791364}
parent 5ac6238b
...@@ -262,6 +262,8 @@ source_set("tests") { ...@@ -262,6 +262,8 @@ source_set("tests") {
} }
deps += [ deps += [
"//device/bluetooth:mocks",
"//services/device/public/cpp/serial:switches",
"//services/device/serial", "//services/device/serial",
"//services/device/serial:test_support", "//services/device/serial:test_support",
] ]
......
...@@ -16,6 +16,10 @@ struct SerialPortInfo { ...@@ -16,6 +16,10 @@ struct SerialPortInfo {
mojo_base.mojom.FilePath path; mojo_base.mojom.FilePath path;
// This member is used to identify whether the SerialPortInfo object is
// converted from a Bluetooth serial device.
DeviceType type;
// On macOS a serial device may have two paths, one for the call-out device // On macOS a serial device may have two paths, one for the call-out device
// and one for the dial-in device. The call-out device is preferred. If // and one for the dial-in device. The call-out device is preferred. If
// there is also an associated dial-in device its path is provided here. If // there is also an associated dial-in device its path is provided here. If
...@@ -80,6 +84,13 @@ enum SerialPortFlushMode { ...@@ -80,6 +84,13 @@ enum SerialPortFlushMode {
kTransmit, kTransmit,
}; };
enum DeviceType {
// The SerialPortInfo object is created from a serial device.
PLATFORM_SERIAL,
// The SerialPortInfo object is created from a Bluetooth SPP device.
SPP_DEVICE,
};
struct SerialConnectionOptions { struct SerialConnectionOptions {
uint32 bitrate = 0; uint32 bitrate = 0;
SerialDataBits data_bits = NONE; SerialDataBits data_bits = NONE;
......
...@@ -21,6 +21,8 @@ if (is_win || (is_linux && use_udev) || is_mac) { ...@@ -21,6 +21,8 @@ if (is_win || (is_linux && use_udev) || is_mac) {
] ]
sources = [ sources = [
"bluetooth_serial_device_enumerator.cc",
"bluetooth_serial_device_enumerator.h",
"buffer.cc", "buffer.cc",
"buffer.h", "buffer.h",
"serial_device_enumerator.cc", "serial_device_enumerator.cc",
...@@ -47,8 +49,11 @@ if (is_win || (is_linux && use_udev) || is_mac) { ...@@ -47,8 +49,11 @@ if (is_win || (is_linux && use_udev) || is_mac) {
deps = [ deps = [
"//base", "//base",
"//device/bluetooth:bluetooth",
"//device/bluetooth/public/cpp",
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
"//net", "//net",
"//services/device/public/cpp/serial:switches",
] ]
if (is_posix) { if (is_posix) {
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/serial/bluetooth_serial_device_enumerator.h"
#include "base/command_line.h"
#include "base/unguessable_token.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/public/mojom/serial.mojom.h"
namespace device {
BluetoothSerialDeviceEnumerator::BluetoothSerialDeviceEnumerator() {
DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBluetoothSerialPortProfileInSerialApi));
device::BluetoothAdapterFactory::Get()->GetClassicAdapter(
base::BindOnce(&BluetoothSerialDeviceEnumerator::OnGotClassicAdapter,
base::Unretained(this)));
}
const BluetoothUUID& GetSerialPortProfileUUID() {
static const BluetoothUUID kValue("1101");
return kValue;
}
BluetoothSerialDeviceEnumerator::~BluetoothSerialDeviceEnumerator() = default;
void BluetoothSerialDeviceEnumerator::OnGotClassicAdapter(
scoped_refptr<device::BluetoothAdapter> adapter) {
DCHECK(adapter);
adapter_ = adapter;
adapter_->AddObserver(this);
BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
for (auto* device : devices) {
BluetoothDevice::UUIDSet device_uuids = device->GetUUIDs();
if (base::Contains(device_uuids, GetSerialPortProfileUUID())) {
auto port = mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->path = base::FilePath::FromUTF8Unsafe(device->GetIdentifier());
port->type = mojom::DeviceType::SPP_DEVICE;
bluetooth_ports_.insert(
std::make_pair(device->GetAddress(), port->token));
AddPort(std::move(port));
}
}
}
void BluetoothSerialDeviceEnumerator::DeviceAdded(BluetoothAdapter* adapter,
BluetoothDevice* device) {
BluetoothDevice::UUIDSet device_uuids = device->GetUUIDs();
if (base::Contains(device_uuids, GetSerialPortProfileUUID())) {
auto port = mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->path = base::FilePath::FromUTF8Unsafe(device->GetIdentifier());
port->type = mojom::DeviceType::SPP_DEVICE;
bluetooth_ports_.insert(std::make_pair(device->GetAddress(), port->token));
AddPort(std::move(port));
}
}
void BluetoothSerialDeviceEnumerator::DeviceRemoved(BluetoothAdapter* adapter,
BluetoothDevice* device) {
auto it = bluetooth_ports_.find(device->GetAddress());
DCHECK(it != bluetooth_ports_.end());
base::UnguessableToken token = it->second;
bluetooth_ports_.erase(it);
RemovePort(token);
}
} // namespace device
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_DEVICE_SERIAL_BLUETOOTH_SERIAL_DEVICE_ENUMERATOR_H_
#define SERVICES_DEVICE_SERIAL_BLUETOOTH_SERIAL_DEVICE_ENUMERATOR_H_
#include <map>
#include "base/memory/scoped_refptr.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "services/device/public/mojom/serial.mojom-forward.h"
#include "services/device/serial/serial_device_enumerator.h"
namespace device {
class BluetoothSerialDeviceEnumerator : public BluetoothAdapter::Observer,
public SerialDeviceEnumerator {
public:
BluetoothSerialDeviceEnumerator();
BluetoothSerialDeviceEnumerator(const BluetoothSerialDeviceEnumerator&) =
delete;
BluetoothSerialDeviceEnumerator& operator=(
const BluetoothSerialDeviceEnumerator&) = delete;
~BluetoothSerialDeviceEnumerator() override;
// BluetoothAdapter::Observer methods:
void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override;
void DeviceRemoved(BluetoothAdapter* adapter,
BluetoothDevice* device) override;
protected:
scoped_refptr<BluetoothAdapter> adapter_;
private:
void OnGotClassicAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
std::unordered_map<std::string, base::UnguessableToken> bluetooth_ports_;
};
} // namespace device
#endif // SERVICES_DEVICE_SERIAL_BLUETOOTH_SERIAL_DEVICE_ENUMERATOR_H_
...@@ -6,9 +6,14 @@ ...@@ -6,9 +6,14 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h"
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/serial/bluetooth_serial_device_enumerator.h"
#include "services/device/serial/serial_device_enumerator.h" #include "services/device/serial/serial_device_enumerator.h"
#include "services/device/serial/serial_port_impl.h" #include "services/device/serial/serial_port_impl.h"
...@@ -34,6 +39,14 @@ void SerialPortManagerImpl::SetSerialEnumeratorForTesting( ...@@ -34,6 +39,14 @@ void SerialPortManagerImpl::SetSerialEnumeratorForTesting(
observed_enumerator_.Add(enumerator_.get()); observed_enumerator_.Add(enumerator_.get());
} }
void SerialPortManagerImpl::SetBluetoothSerialEnumeratorForTesting(
std::unique_ptr<BluetoothSerialDeviceEnumerator>
fake_bluetooth_enumerator) {
DCHECK(fake_bluetooth_enumerator);
bluetooth_enumerator_ = std::move(fake_bluetooth_enumerator);
observed_enumerator_.Add(bluetooth_enumerator_.get());
}
void SerialPortManagerImpl::SetClient( void SerialPortManagerImpl::SetClient(
mojo::PendingRemote<mojom::SerialPortManagerClient> client) { mojo::PendingRemote<mojom::SerialPortManagerClient> client) {
clients_.Add(std::move(client)); clients_.Add(std::move(client));
...@@ -44,7 +57,21 @@ void SerialPortManagerImpl::GetDevices(GetDevicesCallback callback) { ...@@ -44,7 +57,21 @@ void SerialPortManagerImpl::GetDevices(GetDevicesCallback callback) {
enumerator_ = SerialDeviceEnumerator::Create(ui_task_runner_); enumerator_ = SerialDeviceEnumerator::Create(ui_task_runner_);
observed_enumerator_.Add(enumerator_.get()); observed_enumerator_.Add(enumerator_.get());
} }
std::move(callback).Run(enumerator_->GetDevices()); auto devices = enumerator_->GetDevices();
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableBluetoothSerialPortProfileInSerialApi)) {
if (!bluetooth_enumerator_) {
bluetooth_enumerator_ =
std::make_unique<BluetoothSerialDeviceEnumerator>();
observed_enumerator_.Add(bluetooth_enumerator_.get());
}
auto bluetooth_devices = bluetooth_enumerator_->GetDevices();
devices.insert(devices.end(),
std::make_move_iterator(bluetooth_devices.begin()),
std::make_move_iterator(bluetooth_devices.end()));
}
std::move(callback).Run(std::move(devices));
} }
void SerialPortManagerImpl::GetPort( void SerialPortManagerImpl::GetPort(
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h" #include "mojo/public/cpp/bindings/remote_set.h"
#include "services/device/public/mojom/serial.mojom.h" #include "services/device/public/mojom/serial.mojom.h"
#include "services/device/serial/bluetooth_serial_device_enumerator.h"
#include "services/device/serial/serial_device_enumerator.h" #include "services/device/serial/serial_device_enumerator.h"
namespace base { namespace base {
...@@ -38,6 +39,9 @@ class SerialPortManagerImpl : public mojom::SerialPortManager, ...@@ -38,6 +39,9 @@ class SerialPortManagerImpl : public mojom::SerialPortManager,
void Bind(mojo::PendingReceiver<mojom::SerialPortManager> receiver); void Bind(mojo::PendingReceiver<mojom::SerialPortManager> receiver);
void SetSerialEnumeratorForTesting( void SetSerialEnumeratorForTesting(
std::unique_ptr<SerialDeviceEnumerator> fake_enumerator); std::unique_ptr<SerialDeviceEnumerator> fake_enumerator);
void SetBluetoothSerialEnumeratorForTesting(
std::unique_ptr<BluetoothSerialDeviceEnumerator>
fake_bluetooth_enumerator);
private: private:
// mojom::SerialPortManager methods: // mojom::SerialPortManager methods:
...@@ -55,6 +59,7 @@ class SerialPortManagerImpl : public mojom::SerialPortManager, ...@@ -55,6 +59,7 @@ class SerialPortManagerImpl : public mojom::SerialPortManager,
void OnPortRemoved(const mojom::SerialPortInfo& port) override; void OnPortRemoved(const mojom::SerialPortInfo& port) override;
std::unique_ptr<SerialDeviceEnumerator> enumerator_; std::unique_ptr<SerialDeviceEnumerator> enumerator_;
std::unique_ptr<BluetoothSerialDeviceEnumerator> bluetooth_enumerator_;
ScopedObserver<SerialDeviceEnumerator, SerialDeviceEnumerator::Observer> ScopedObserver<SerialDeviceEnumerator, SerialDeviceEnumerator::Observer>
observed_enumerator_{this}; observed_enumerator_{this};
......
...@@ -10,16 +10,23 @@ ...@@ -10,16 +10,23 @@
#include <vector> #include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/test/bind_test_util.h" #include "base/test/bind_test_util.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "services/device/device_service_test_base.h" #include "services/device/device_service_test_base.h"
#include "services/device/public/cpp/serial/serial_switches.h"
#include "services/device/public/mojom/serial.mojom.h" #include "services/device/public/mojom/serial.mojom.h"
#include "services/device/serial/bluetooth_serial_device_enumerator.h"
#include "services/device/serial/fake_serial_device_enumerator.h" #include "services/device/serial/fake_serial_device_enumerator.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -33,6 +40,7 @@ namespace { ...@@ -33,6 +40,7 @@ namespace {
const base::FilePath kFakeDevicePath1(FILE_PATH_LITERAL("/dev/fakeserialmojo")); const base::FilePath kFakeDevicePath1(FILE_PATH_LITERAL("/dev/fakeserialmojo"));
const base::FilePath kFakeDevicePath2(FILE_PATH_LITERAL("\\\\COM800\\")); const base::FilePath kFakeDevicePath2(FILE_PATH_LITERAL("\\\\COM800\\"));
constexpr char kDeviceAddress[] = "00:00:00:00:00:00";
class MockSerialPortManagerClient : public mojom::SerialPortManagerClient { class MockSerialPortManagerClient : public mojom::SerialPortManagerClient {
public: public:
...@@ -72,8 +80,37 @@ class SerialPortManagerImplTest : public DeviceServiceTestBase { ...@@ -72,8 +80,37 @@ class SerialPortManagerImplTest : public DeviceServiceTestBase {
~SerialPortManagerImplTest() override = default; ~SerialPortManagerImplTest() override = default;
// Since not all functions need to use a MockBluetoothAdapter, this function
// is called at the beginning of test cases that do require a
// MockBluetoothAdapter.
void SetupBluetoothEnumerator() {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableBluetoothSerialPortProfileInSerialApi);
ON_CALL(*adapter_, GetDevices())
.WillByDefault(
Invoke(adapter_.get(), &MockBluetoothAdapter::GetConstMockDevices));
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
auto mock_device = std::make_unique<MockBluetoothDevice>(
adapter_.get(), 0, "Test Device", kDeviceAddress, false, false);
static const BluetoothUUID kSerialPortProfileUUID("1101");
mock_device->AddUUID(kSerialPortProfileUUID);
adapter_->AddMockDevice(std::move(mock_device));
auto bluetooth_enumerator =
std::make_unique<BluetoothSerialDeviceEnumerator>();
bluetooth_enumerator_ = bluetooth_enumerator.get();
manager_->SetBluetoothSerialEnumeratorForTesting(
std::move(bluetooth_enumerator));
}
protected: protected:
FakeSerialEnumerator* enumerator_; FakeSerialEnumerator* enumerator_;
BluetoothSerialDeviceEnumerator* bluetooth_enumerator_;
scoped_refptr<MockBluetoothAdapter> adapter_ =
base::MakeRefCounted<MockBluetoothAdapter>();
void Bind(mojo::PendingReceiver<mojom::SerialPortManager> receiver) { void Bind(mojo::PendingReceiver<mojom::SerialPortManager> receiver) {
manager_->Bind(std::move(receiver)); manager_->Bind(std::move(receiver));
...@@ -88,6 +125,8 @@ class SerialPortManagerImplTest : public DeviceServiceTestBase { ...@@ -88,6 +125,8 @@ class SerialPortManagerImplTest : public DeviceServiceTestBase {
// This is to simply test that we can enumerate devices on the platform without // This is to simply test that we can enumerate devices on the platform without
// hanging or crashing. // hanging or crashing.
TEST_F(SerialPortManagerImplTest, SimpleConnectTest) { TEST_F(SerialPortManagerImplTest, SimpleConnectTest) {
// DeviceService has its own instance of SerialPortManagerImpl that is used to
// bind the receiver over the one created for this test.
mojo::Remote<mojom::SerialPortManager> port_manager; mojo::Remote<mojom::SerialPortManager> port_manager;
device_service()->BindSerialPortManager( device_service()->BindSerialPortManager(
port_manager.BindNewPipeAndPassReceiver()); port_manager.BindNewPipeAndPassReceiver());
...@@ -112,10 +151,14 @@ TEST_F(SerialPortManagerImplTest, SimpleConnectTest) { ...@@ -112,10 +151,14 @@ TEST_F(SerialPortManagerImplTest, SimpleConnectTest) {
} }
TEST_F(SerialPortManagerImplTest, GetDevices) { TEST_F(SerialPortManagerImplTest, GetDevices) {
SetupBluetoothEnumerator();
mojo::Remote<mojom::SerialPortManager> port_manager; mojo::Remote<mojom::SerialPortManager> port_manager;
Bind(port_manager.BindNewPipeAndPassReceiver()); Bind(port_manager.BindNewPipeAndPassReceiver());
const std::set<base::FilePath> expected_paths = {kFakeDevicePath1, const std::string address_identifier =
kFakeDevicePath2}; std::string(kDeviceAddress) + "-Identifier";
const std::set<base::FilePath> expected_paths = {
kFakeDevicePath1, kFakeDevicePath2,
base::FilePath::FromUTF8Unsafe(address_identifier)};
base::RunLoop loop; base::RunLoop loop;
port_manager->GetDevices(base::BindLambdaForTesting( port_manager->GetDevices(base::BindLambdaForTesting(
...@@ -131,6 +174,7 @@ TEST_F(SerialPortManagerImplTest, GetDevices) { ...@@ -131,6 +174,7 @@ TEST_F(SerialPortManagerImplTest, GetDevices) {
} }
TEST_F(SerialPortManagerImplTest, PortRemovedAndAdded) { TEST_F(SerialPortManagerImplTest, PortRemovedAndAdded) {
SetupBluetoothEnumerator();
mojo::Remote<mojom::SerialPortManager> port_manager; mojo::Remote<mojom::SerialPortManager> port_manager;
Bind(port_manager.BindNewPipeAndPassReceiver()); Bind(port_manager.BindNewPipeAndPassReceiver());
...@@ -180,6 +224,7 @@ TEST_F(SerialPortManagerImplTest, PortRemovedAndAdded) { ...@@ -180,6 +224,7 @@ TEST_F(SerialPortManagerImplTest, PortRemovedAndAdded) {
} }
TEST_F(SerialPortManagerImplTest, GetPort) { TEST_F(SerialPortManagerImplTest, GetPort) {
SetupBluetoothEnumerator();
mojo::Remote<mojom::SerialPortManager> port_manager; mojo::Remote<mojom::SerialPortManager> port_manager;
Bind(port_manager.BindNewPipeAndPassReceiver()); Bind(port_manager.BindNewPipeAndPassReceiver());
...@@ -202,4 +247,69 @@ TEST_F(SerialPortManagerImplTest, GetPort) { ...@@ -202,4 +247,69 @@ TEST_F(SerialPortManagerImplTest, GetPort) {
loop.Run(); loop.Run();
} }
TEST_F(SerialPortManagerImplTest, BluetoothPortRemovedAndAdded) {
SetupBluetoothEnumerator();
mojo::Remote<mojom::SerialPortManager> port_manager;
Bind(port_manager.BindNewPipeAndPassReceiver());
MockSerialPortManagerClient client;
port_manager->SetClient(client.BindNewPipeAndPassRemote());
const std::string address_identifier =
std::string(kDeviceAddress) + "-Identifier";
base::UnguessableToken port1_token;
{
base::RunLoop run_loop;
port_manager->GetDevices(base::BindLambdaForTesting(
[&](std::vector<mojom::SerialPortInfoPtr> results) {
for (const auto& port : results) {
if (port->path ==
base::FilePath::FromUTF8Unsafe(address_identifier)) {
port1_token = port->token;
break;
}
}
run_loop.Quit();
}));
run_loop.Run();
}
ASSERT_FALSE(port1_token.is_empty());
bluetooth_enumerator_->DeviceRemoved(
adapter_.get(), adapter_->RemoveMockDevice(kDeviceAddress).get());
{
base::RunLoop run_loop;
EXPECT_CALL(client, OnPortRemoved(_))
.WillOnce(Invoke([&](mojom::SerialPortInfoPtr port) {
EXPECT_EQ(port1_token, port->token);
EXPECT_EQ(port->path,
base::FilePath::FromUTF8Unsafe(address_identifier));
EXPECT_EQ(mojom::DeviceType::SPP_DEVICE, port->type);
run_loop.Quit();
}));
run_loop.Run();
}
auto mock_device = std::make_unique<MockBluetoothDevice>(
adapter_.get(), 0, "Test Device", kDeviceAddress, false, false);
static const BluetoothUUID kSerialPortProfileUUID("1101");
mock_device->AddUUID(kSerialPortProfileUUID);
MockBluetoothDevice* mock_device_ptr = mock_device.get();
adapter_->AddMockDevice(std::move(mock_device));
bluetooth_enumerator_->DeviceAdded(adapter_.get(), mock_device_ptr);
{
base::RunLoop run_loop;
EXPECT_CALL(client, OnPortAdded(_))
.WillOnce(Invoke([&](mojom::SerialPortInfoPtr port) {
EXPECT_NE(port1_token, port->token);
EXPECT_EQ(port->path,
base::FilePath::FromUTF8Unsafe(address_identifier));
EXPECT_EQ(mojom::DeviceType::SPP_DEVICE, port->type);
run_loop.Quit();
}));
run_loop.Run();
}
}
} // namespace device } // namespace device
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