blob: 2ba28f9b9e70b83d3dc8b95cf7e4226a40250169 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/serial/serial_chooser_controller.h"
#include <memory>
#include <utility>
#include "base/functional/callback_helpers.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "build/build_config.h"
#include "chrome/browser/serial/serial_blocklist.h"
#include "chrome/browser/serial/serial_chooser_context.h"
#include "chrome/browser/serial/serial_chooser_context_factory.h"
#include "chrome/browser/serial/serial_chooser_histograms.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/permissions/mock_chooser_controller_view.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 "mojo/public/cpp/bindings/pending_remote.h"
#include "services/device/public/cpp/bluetooth/bluetooth_utils.h"
#include "services/device/public/cpp/test/fake_serial_port_manager.h"
#include "services/device/public/mojom/serial.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/serial/serial.mojom.h"
namespace {
using ::device::BluetoothUUID;
using ::device::MockBluetoothAdapter;
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
constexpr char kBluetoothDevice1Address[] = "00:11:22:33:44:55";
constexpr char16_t kBluetoothDevice1Name[] = u"Bluetooth #1";
constexpr char kBluetoothDevice2Address[] = "11:22:33:44:55:66";
constexpr char16_t kBluetoothDevice2Name[] = u"Bluetooth #2";
const BluetoothUUID kRandomBluetoothServiceClassId(
"34a0fe08-1c1f-4251-879e-2a8c397e56ee");
device::mojom::SerialPortInfoPtr CreateBluetoothPort(
const std::string& device_address,
const std::u16string& device_name,
const BluetoothUUID& service_class_id) {
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->path = base::FilePath::FromUTF8Unsafe(device_address);
port->type = device::mojom::SerialPortType::BLUETOOTH_CLASSIC_RFCOMM;
port->bluetooth_service_class_id = service_class_id;
port->display_name = base::UTF16ToUTF8(device_name);
return port;
}
} // namespace
class SerialChooserControllerTest : public ChromeRenderViewHostTestHarness {
public:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
mojo::PendingRemote<device::mojom::SerialPortManager> port_manager;
port_manager_.AddReceiver(port_manager.InitWithNewPipeAndPassReceiver());
SerialChooserContextFactory::GetForProfile(profile())
->SetPortManagerForTesting(std::move(port_manager));
adapter_ = base::MakeRefCounted<testing::NiceMock<MockBluetoothAdapter>>();
device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
ON_CALL(*adapter_, IsPresent).WillByDefault(Return(true));
ON_CALL(*adapter_, GetOsPermissionStatus)
.WillByDefault(
Return(device::BluetoothAdapter::PermissionStatus::kAllowed));
ON_CALL(*adapter_, IsPowered).WillByDefault(Return(true));
}
base::UnguessableToken AddBluetoothPort(
const std::string& device_address,
const std::u16string& device_name,
const BluetoothUUID& service_class_id) {
auto port =
CreateBluetoothPort(device_address, device_name, service_class_id);
base::UnguessableToken port_token = port->token;
port_manager().AddPort(std::move(port));
return port_token;
}
base::UnguessableToken AddPort(
const std::string& display_name,
const base::FilePath& path,
std::optional<uint16_t> vendor_id = std::nullopt,
std::optional<uint16_t> product_id = std::nullopt) {
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->display_name = display_name;
port->path = path;
if (vendor_id) {
port->has_vendor_id = true;
port->vendor_id = *vendor_id;
}
if (product_id) {
port->has_product_id = true;
port->product_id = *product_id;
}
base::UnguessableToken port_token = port->token;
port_manager().AddPort(std::move(port));
return port_token;
}
device::FakeSerialPortManager& port_manager() { return port_manager_; }
MockBluetoothAdapter* adapter() { return adapter_.get(); }
private:
device::FakeSerialPortManager port_manager_;
scoped_refptr<MockBluetoothAdapter> adapter_;
};
TEST_F(SerialChooserControllerTest, GetPortsLateResponse) {
std::vector<blink::mojom::SerialPortFilterPtr> filters;
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
bool callback_run = false;
auto callback = base::BindLambdaForTesting(
[&](device::mojom::SerialPortInfoPtr port_info) {
EXPECT_FALSE(port_info);
callback_run = true;
});
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), std::move(callback));
controller.reset();
// Allow any tasks posted by |controller| to run, such as asynchronous
// requests to the Device Service to get the list of available serial ports.
// These should be safely discarded since |controller| was destroyed.
base::RunLoop().RunUntilIdle();
// Even if |controller| is destroyed without user interaction the callback
// should be run.
EXPECT_TRUE(callback_run);
}
TEST_F(SerialChooserControllerTest, PortsAddedAndRemoved) {
base::HistogramTester histogram_tester;
std::vector<blink::mojom::SerialPortFilterPtr> filters;
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_EQ(0u, controller->NumOptions());
auto port = device::mojom::SerialPortInfo::New();
port->token = base::UnguessableToken::Create();
port->display_name = "Test Port 1";
port->path = base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0"));
#if BUILDFLAG(IS_MAC)
// This path will be ignored and not generate additional chooser entries or
// be displayed in the device name.
port->alternate_path = base::FilePath(FILE_PATH_LITERAL("/dev/alternateS0"));
#endif
base::UnguessableToken port1_token = port->token;
port_manager().AddPort(std::move(port));
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionAdded(_)).WillOnce(Invoke([&](size_t index) {
EXPECT_EQ(0u, index);
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(u"Test Port 1 (ttyS0)", controller->GetOption(0));
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")));
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionAdded(_)).WillOnce(Invoke([&](size_t index) {
EXPECT_EQ(1u, index);
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_EQ(2u, controller->NumOptions());
EXPECT_EQ(u"Test Port 1 (ttyS0)", controller->GetOption(0));
EXPECT_EQ(u"Test Port 2 (ttyS1)", controller->GetOption(1));
port_manager().RemovePort(port1_token);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionRemoved(_)).WillOnce(Invoke([&](size_t index) {
EXPECT_EQ(0u, index);
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(u"Test Port 2 (ttyS1)", controller->GetOption(0));
controller.reset();
histogram_tester.ExpectUniqueSample("Permissions.Serial.ChooserClosed",
SerialChooserOutcome::kCancelled, 1);
}
TEST_F(SerialChooserControllerTest, PortSelected) {
base::HistogramTester histogram_tester;
base::UnguessableToken port_token =
AddPort("Test Port", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")));
base::MockCallback<content::SerialChooser::Callback> callback;
std::vector<blink::mojom::SerialPortFilterPtr> filters;
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), callback.Get());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(u"Test Port (ttyS0)", controller->GetOption(0));
run_loop.Quit();
}));
run_loop.Run();
}
EXPECT_CALL(callback, Run(_))
.WillOnce(Invoke([&](device::mojom::SerialPortInfoPtr port) {
EXPECT_EQ(port_token, port->token);
// Regression test for https://crbug.com/1069057. Ensure that the set of
// options is still valid after the callback is run.
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(u"Test Port (ttyS0)", controller->GetOption(0));
}));
controller->Select({0});
histogram_tester.ExpectUniqueSample(
"Permissions.Serial.ChooserClosed",
SerialChooserOutcome::kEphemeralPermissionGranted, 1);
}
TEST_F(SerialChooserControllerTest, PortFiltered) {
base::HistogramTester histogram_tester;
// Create two ports from the same vendor with different product IDs.
base::UnguessableToken port_1 =
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
base::UnguessableToken port_2 =
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
0x1234, 0x0002);
// and a Bluetooth port which should always be ignored for this test.
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
device::GetSerialPortProfileUUID());
// Create a filter which will select only the first port.
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->has_vendor_id = true;
filter->vendor_id = 0x1234;
filter->has_product_id = true;
filter->product_id = 0x0001;
filters.push_back(std::move(filter));
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
// Expect that only the first port is shown thanks to the filter.
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(u"Test Port 1 (ttyS0)", controller->GetOption(0));
run_loop.Quit();
}));
run_loop.Run();
}
// Removing the second port should be a no-op since it is filtered out.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(port_2);
base::RunLoop().RunUntilIdle();
// Adding it back should be a no-op as well.
EXPECT_CALL(view, OnOptionAdded).Times(0);
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
0x1234, 0x0002);
base::RunLoop().RunUntilIdle();
// Removing the first port should trigger a change in the UI. This also acts
// as a synchronization point to make sure that the changes above were
// processed.
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionRemoved(0)).WillOnce(Invoke([&]() {
run_loop.Quit();
}));
port_manager().RemovePort(port_1);
run_loop.Run();
}
}
TEST_F(SerialChooserControllerTest, BluetoothPortFiltered) {
base::HistogramTester histogram_tester;
// Create a wired and a Bluetooth port.
base::UnguessableToken port_1 =
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
base::UnguessableToken bluetooth_port_token =
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
device::GetSerialPortProfileUUID());
// Create a filter which will select only the Bluetooth port.
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = device::GetSerialPortProfileUUID();
filters.push_back(std::move(filter));
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
const std::u16string expected_name = kBluetoothDevice1Name;
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
run_loop.Quit();
}));
run_loop.Run();
// Expect that only the Bluetooth port is shown thanks to the filter.
ASSERT_EQ(1u, controller->NumOptions());
ASSERT_EQ(expected_name, controller->GetOption(0));
}
// Removing the wired port should be a no-op since it is filtered out.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(port_1);
base::RunLoop().RunUntilIdle();
// Adding it back should be a no-op as well.
EXPECT_CALL(view, OnOptionAdded).Times(0);
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
base::RunLoop().RunUntilIdle();
// Removing the Bluetooth port should trigger a change in the UI. This also
// acts as a synchronization point to make sure that the changes above were
// processed.
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionRemoved(0))
.WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure()));
port_manager().RemovePort(bluetooth_port_token);
run_loop.Run();
}
}
TEST_F(SerialChooserControllerTest, BluetoothPortFilteredButNotAllowed) {
base::HistogramTester histogram_tester;
base::UnguessableToken port_1 =
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
// Create a non-standard Bluetooth port.
base::UnguessableToken bluetooth_port_token =
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
kRandomBluetoothServiceClassId);
// Create a filter which will select only the Bluetooth port.
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
const std::u16string expected_name = kBluetoothDevice1Name;
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
// Expect that only the Bluetooth port is not shown as it is not allowed.
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
// Removing the wired port should be a no-op since it is filtered out.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(port_1);
base::RunLoop().RunUntilIdle();
// Adding it back should be a no-op as well.
EXPECT_CALL(view, OnOptionAdded).Times(0);
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
base::RunLoop().RunUntilIdle();
// Removing the wired port should be a no-op since it is not allowed.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(bluetooth_port_token);
base::RunLoop().RunUntilIdle();
// Adding it back should be a no-op as well.
EXPECT_CALL(view, OnOptionAdded).Times(0);
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
kRandomBluetoothServiceClassId);
base::RunLoop().RunUntilIdle();
}
TEST_F(SerialChooserControllerTest, DeviceNameDisambiguation) {
base::HistogramTester histogram_tester;
// A test Bluetooth service ID to be chosen.
const BluetoothUUID kDevice1OtherServiceId(
"105b5a98-d8e6-4f39-9432-49ae7529de74");
// Create the test Bluetooth ports.
auto port1_token =
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
device::GetSerialPortProfileUUID());
auto port2_token = AddBluetoothPort(
kBluetoothDevice1Address, kBluetoothDevice1Name, kDevice1OtherServiceId);
auto port3_token =
AddBluetoothPort(kBluetoothDevice2Address, kBluetoothDevice2Name,
device::GetSerialPortProfileUUID());
// Create a filter that selects all Bluetooth ports.
std::vector<blink::mojom::SerialPortFilterPtr> filters;
{
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = device::GetSerialPortProfileUUID();
filters.push_back(std::move(filter));
filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kDevice1OtherServiceId;
filters.push_back(std::move(filter));
}
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
allowed_bluetooth_service_class_ids.push_back(kDevice1OtherServiceId);
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(3u, controller->NumOptions());
for (size_t idx = 0; idx < controller->NumOptions(); idx++) {
const device::mojom::SerialPortInfo& port_info =
controller->GetPortForTest(idx);
std::u16string expected_name;
if (port_info.token == port1_token) {
expected_name =
u"Bluetooth #1 (00001101-0000-1000-8000-00805f9b34fb)";
} else if (port_info.token == port2_token) {
expected_name =
u"Bluetooth #1 (105b5a98-d8e6-4f39-9432-49ae7529de74)";
} else if (port_info.token == port3_token) {
// Only one port for device #2, so expect device display name only.
expected_name = u"Bluetooth #2";
} else {
FAIL() << "Unexpected port token: " << port_info.token;
}
EXPECT_EQ(expected_name, controller->GetOption(idx))
<< "incorrect name for index " << idx;
}
run_loop.Quit();
}));
run_loop.Run();
}
}
class SerialChooserControllerTestWithBlockedPorts
: public SerialChooserControllerTest {
public:
SerialChooserControllerTestWithBlockedPorts() {
feature_list_.InitWithFeaturesAndParameters(
{{kWebSerialBlocklist,
{{kWebSerialBlocklistAdditions.name,
"usb:1234:0002,bluetooth:aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"}}}},
{});
// Reinitialize SerialBlocklist in case it was already initialized by
// another test.
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
~SerialChooserControllerTestWithBlockedPorts() override {
// Clear the blocklist so that later tests are unaffected.
feature_list_.Reset();
SerialBlocklist::Get().ResetToDefaultValuesForTesting();
}
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SerialChooserControllerTestWithBlockedPorts, Blocklist) {
// Create two ports from the same vendor with different product IDs. The
// second one is on the blocklist.
base::UnguessableToken port_1 =
AddPort("Test Port 1", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS0")),
0x1234, 0x0001);
base::UnguessableToken port_2 =
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
0x1234, 0x0002);
const BluetoothUUID kBlockedBluetoothServiceClassId(
"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
base::UnguessableToken port_3 =
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
kBlockedBluetoothServiceClassId);
std::vector<blink::mojom::SerialPortFilterPtr> filters;
std::vector<device::BluetoothUUID> allowed_bluetooth_service_class_ids;
allowed_bluetooth_service_class_ids.push_back(
kBlockedBluetoothServiceClassId);
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::move(allowed_bluetooth_service_class_ids), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
// Expect that only the first port is shown thanks to the filter.
EXPECT_EQ(1u, controller->NumOptions());
EXPECT_EQ(u"Test Port 1 (ttyS0)", controller->GetOption(0));
run_loop.Quit();
}));
run_loop.Run();
}
// Removing the second port should be a no-op since it is filtered out.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(port_2);
base::RunLoop().RunUntilIdle();
// Removing the third port should be a no-op since it is filtered out.
EXPECT_CALL(view, OnOptionRemoved).Times(0);
port_manager().RemovePort(port_3);
base::RunLoop().RunUntilIdle();
// Adding it back should be a no-op as well.
EXPECT_CALL(view, OnOptionAdded).Times(0);
AddPort("Test Port 2", base::FilePath(FILE_PATH_LITERAL("/dev/ttyS1")),
0x1234, 0x0002);
base::RunLoop().RunUntilIdle();
// Removing the first port should trigger a change in the UI. This also acts
// as a synchronization point to make sure that the changes above were
// processed.
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionRemoved(0)).WillOnce(Invoke([&]() {
run_loop.Quit();
}));
port_manager().RemovePort(port_1);
run_loop.Run();
}
}
TEST_F(SerialChooserControllerTest,
NotWirelessSerialPortExclusiveNoBluetoothServiceClassIds) {
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::vector<blink::mojom::SerialPortFilterPtr>(),
std::vector<device::BluetoothUUID>(), base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
// Check that there is no interaction with the bluetooth adapter.
EXPECT_CALL(*adapter(), GetOsPermissionStatus).Times(0);
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SerialChooserControllerTest,
NotWirelessSerialPortExclusiveEmptyFilters) {
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::vector<blink::mojom::SerialPortFilterPtr>(),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
// Check that there is no interaction with the bluetooth adapter.
EXPECT_CALL(*adapter(), GetOsPermissionStatus).Times(0);
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SerialChooserControllerTest,
NotWirelessSerialPortExclusiveFiltersWithBluetoothAndUsb) {
// Create filters that can match wired and wireless serial ports.
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
filter = blink::mojom::SerialPortFilter::New();
filter->has_vendor_id = true;
filter->vendor_id = 0x1234;
filter->has_product_id = true;
filter->product_id = 0x0001;
filters.push_back(std::move(filter));
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
// Check that there is no interaction with the bluetooth adapter.
EXPECT_CALL(*adapter(), GetOsPermissionStatus).Times(0);
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
#if BUILDFLAG(IS_ANDROID)
#define MAYBE_BluetoothAdapterNotPresent BluetoothAdapterNotPresent
#else
#define MAYBE_BluetoothAdapterNotPresent DISABLED_BluetoothAdapterNotPresent
#endif // BUILDFLAG(IS_ANDROID)
TEST_F(SerialChooserControllerTest, MAYBE_BluetoothAdapterNotPresent) {
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
EXPECT_CALL(*adapter(), IsPresent).WillOnce(Return(false));
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SerialChooserControllerTest, SystemBluetoothPermissionDenied) {
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
EXPECT_CALL(*adapter(), GetOsPermissionStatus)
.WillOnce(Return(device::BluetoothAdapter::PermissionStatus::kDenied));
EXPECT_CALL(view, OnAdapterAuthorizationChanged(false)).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SerialChooserControllerTest, SystemBluetoothPermissionUndetermined) {
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
EXPECT_CALL(*adapter(), GetOsPermissionStatus)
.WillOnce(
Return(device::BluetoothAdapter::PermissionStatus::kUndetermined));
EXPECT_CALL(view, OnAdapterAuthorizationChanged(false)).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SerialChooserControllerTest, RefreshOptionsAfterSystemBluetoothPermissionGranted) {
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
base::RunLoop run_loop;
EXPECT_CALL(*adapter(), GetOsPermissionStatus)
.WillOnce(Return(device::BluetoothAdapter::PermissionStatus::kDenied));
EXPECT_CALL(view, OnAdapterAuthorizationChanged(false)).WillOnce(Invoke([&] {
EXPECT_CALL(*adapter(), GetOsPermissionStatus)
.WillOnce(Return(device::BluetoothAdapter::PermissionStatus::kAllowed));
controller->RefreshOptions();
}));
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(SerialChooserControllerTest, AdapterPowerOffThenPowerOn) {
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
kRandomBluetoothServiceClassId);
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
// Start with adapter power off.
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(*adapter(), IsPowered).WillOnce(Return(false));
EXPECT_CALL(view, OnAdapterEnabledChanged(false)).WillOnce(Invoke([&] {
EXPECT_EQ(0u, controller->NumOptions());
run_loop.Quit();
}));
run_loop.Run();
}
// Then adapter power on, expect to see ports.
EXPECT_CALL(*adapter(), IsPowered).WillOnce(Return(true));
EXPECT_CALL(view, OnAdapterEnabledChanged(true));
adapter()->NotifyAdapterPoweredChanged(true);
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
ASSERT_EQ(1u, controller->NumOptions());
EXPECT_EQ(kBluetoothDevice1Name, controller->GetOption(0));
run_loop.Quit();
}));
run_loop.Run();
}
}
TEST_F(SerialChooserControllerTest, AdapterPowerOffAfterOptionsInitialized) {
AddBluetoothPort(kBluetoothDevice1Address, kBluetoothDevice1Name,
kRandomBluetoothServiceClassId);
std::vector<blink::mojom::SerialPortFilterPtr> filters;
auto filter = blink::mojom::SerialPortFilter::New();
filter->bluetooth_service_class_id = kRandomBluetoothServiceClassId;
filters.push_back(std::move(filter));
// Start with adapter power on.
auto controller = std::make_unique<SerialChooserController>(
main_rfh(), std::move(filters),
std::vector<device::BluetoothUUID>({kRandomBluetoothServiceClassId}),
base::DoNothing());
permissions::MockChooserControllerView view;
controller->set_view(&view);
{
base::RunLoop run_loop;
EXPECT_CALL(*adapter(), IsPowered).WillOnce(Return(true));
EXPECT_CALL(view, OnOptionsInitialized).WillOnce(Invoke([&] {
ASSERT_EQ(1u, controller->NumOptions());
EXPECT_EQ(kBluetoothDevice1Name, controller->GetOption(0));
run_loop.Quit();
}));
run_loop.Run();
}
// Then adapter power off.
{
base::RunLoop run_loop;
EXPECT_CALL(view, OnAdapterEnabledChanged(false)).WillOnce(Invoke([&] {
run_loop.Quit();
}));
adapter()->NotifyAdapterPoweredChanged(false);
run_loop.Run();
}
}