// 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/ash/usb/cros_usb_detector.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "ash/components/arc/arc_util.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/gmock_move_support.h"
#include "chrome/browser/ash/arc/arc_util.h"
#include "chrome/browser/ash/bruschetta/bruschetta_util.h"
#include "chrome/browser/ash/crostini/crostini_manager.h"
#include "chrome/browser/ash/crostini/crostini_pref_names.h"
#include "chrome/browser/ash/crostini/crostini_test_helper.h"
#include "chrome/browser/ash/crostini/fake_crostini_features.h"
#include "chrome/browser/ash/guest_os/guest_id.h"
#include "chrome/browser/ash/plugin_vm/fake_plugin_vm_features.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_test_helper.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/system_notification_helper.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "chromeos/ash/components/dbus/cros_disks/cros_disks_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "chromeos/ash/components/dbus/vm_plugin_dispatcher/fake_vm_plugin_dispatcher_client.h"
#include "chromeos/ash/components/disks/disk.h"
#include "chromeos/ash/components/disks/disk_mount_manager.h"
#include "chromeos/ash/components/disks/mock_disk_mount_manager.h"
#include "services/device/public/cpp/test/fake_usb_device_info.h"
#include "services/device/public/cpp/test/fake_usb_device_manager.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_manager.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/public/cpp/notification.h"
#include "url/gurl.h"
namespace ash {
namespace {
using testing::_;
using MountCallback = ::base::OnceCallback<void(MountError)>;
const char* kCrostiniTestContainerName = "test-container";
// USB device product name.
const char* kProductName_1 = "Google Product A";
const char* kProductName_2 = "Google Product B";
const char* kProductName_3 = "Google Product C";
const char* kUnknownProductName = "USB device";
const char* kManufacturerName = "Google";
const int kUsbConfigWithInterfaces = 1;
struct InterfaceCodes {
InterfaceCodes(uint8_t device_class,
uint8_t subclass_code,
uint8_t protocol_code)
: device_class(device_class),
subclass_code(subclass_code),
protocol_code(protocol_code) {}
uint8_t device_class;
uint8_t subclass_code;
uint8_t protocol_code;
};
scoped_refptr<device::FakeUsbDeviceInfo> CreateTestDeviceFromCodes(
uint8_t device_class,
const std::vector<InterfaceCodes>& interface_codes) {
auto config = device::mojom::UsbConfigurationInfo::New();
config->configuration_value = kUsbConfigWithInterfaces;
// The usb_utils do not filter by device class, only by configurations, and
// the FakeUsbDeviceInfo does not set up configurations for a fake device's
// class code. This helper sets up a configuration to match a devices class
// code so that USB devices can be filtered out.
for (size_t i = 0; i < interface_codes.size(); ++i) {
auto alternate = device::mojom::UsbAlternateInterfaceInfo::New();
alternate->alternate_setting = 0;
alternate->class_code = interface_codes[i].device_class;
alternate->subclass_code = interface_codes[i].subclass_code;
alternate->protocol_code = interface_codes[i].protocol_code;
auto interface = device::mojom::UsbInterfaceInfo::New();
interface->interface_number = i;
interface->alternates.push_back(std::move(alternate));
config->interfaces.push_back(std::move(interface));
}
std::vector<device::mojom::UsbConfigurationInfoPtr> configs;
configs.push_back(std::move(config));
scoped_refptr<device::FakeUsbDeviceInfo> device =
new device::FakeUsbDeviceInfo(/*vendor_id*/ 0, /*product_id*/ 1,
device_class, std::move(configs));
device->SetActiveConfig(kUsbConfigWithInterfaces);
return device;
}
scoped_refptr<device::FakeUsbDeviceInfo> CreateTestDeviceOfClass(
uint8_t device_class) {
return CreateTestDeviceFromCodes(device_class,
{InterfaceCodes(device_class, 0xff, 0xff)});
}
class TestCrosUsbDeviceObserver : public CrosUsbDeviceObserver {
public:
void OnUsbDevicesChanged() override { ++notify_count_; }
int notify_count() const { return notify_count_; }
private:
int notify_count_ = 0;
};
} // namespace
class CrosUsbDetectorTest : public BrowserWithTestWindowTest {
public:
CrosUsbDetectorTest() {
// Needed for GuestOsStabilityMonitor.
ChunneldClient::InitializeFake();
ash::CiceroneClient::InitializeFake();
ConciergeClient::InitializeFake();
SeneschalClient::InitializeFake();
VmPluginDispatcherClient::InitializeFake();
fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
fake_concierge_client_ = FakeConciergeClient::Get();
fake_vm_plugin_dispatcher_client_ =
static_cast<FakeVmPluginDispatcherClient*>(
VmPluginDispatcherClient::Get());
mock_disk_mount_manager_ =
new testing::NiceMock<disks::MockDiskMountManager>;
disks::DiskMountManager::InitializeForTesting(mock_disk_mount_manager_);
}
CrosUsbDetectorTest(const CrosUsbDetectorTest&) = delete;
CrosUsbDetectorTest& operator=(const CrosUsbDetectorTest&) = delete;
~CrosUsbDetectorTest() override {
disks::DiskMountManager::Shutdown();
VmPluginDispatcherClient::Shutdown();
SeneschalClient::Shutdown();
ConciergeClient::Shutdown();
ash::CiceroneClient::Shutdown();
ChunneldClient::Shutdown();
}
void SetUp() override {
cros_usb_detector_ = std::make_unique<CrosUsbDetector>();
BrowserWithTestWindowTest::SetUp();
crostini_test_helper_ =
std::make_unique<crostini::CrostiniTestHelper>(profile());
TestingBrowserProcess::GetGlobal()->SetSystemNotificationHelper(
std::make_unique<SystemNotificationHelper>());
display_service_ = std::make_unique<NotificationDisplayServiceTester>(
nullptr /* profile */);
// Set a fake USB device manager before ConnectToDeviceManager().
mojo::PendingRemote<device::mojom::UsbDeviceManager> device_manager;
device_manager_.AddReceiver(
device_manager.InitWithNewPipeAndPassReceiver());
CrosUsbDetector::Get()->SetDeviceManagerForTesting(
std::move(device_manager));
// Create a default VM instance which is running.
crostini::CrostiniManager::GetForProfile(profile())->AddRunningVmForTesting(
crostini::kCrostiniDefaultVmName);
}
void TearDown() override {
crostini_test_helper_.reset();
BrowserWithTestWindowTest::TearDown();
cros_usb_detector_.reset();
}
void ConnectToDeviceManager() {
CrosUsbDetector::Get()->ConnectToDeviceManager();
}
MOCK_METHOD1(OnAttach, void(bool success));
void AttachDeviceToGuest(const guest_os::GuestId& guest_id,
const std::string& guid,
bool vm_success = true,
bool container_success = true) {
std::optional<vm_tools::concierge::AttachUsbDeviceResponse>
attach_device_response;
attach_device_response.emplace();
attach_device_response->set_success(vm_success);
attach_device_response->set_guest_port(0);
fake_concierge_client_->set_attach_usb_device_response(
std::move(attach_device_response));
if (vm_success && !guest_id.container_name.empty()) {
vm_tools::cicerone::AttachUsbToContainerResponse
attach_container_response;
attach_container_response.set_status(
container_success
? vm_tools::cicerone::AttachUsbToContainerResponse_Status_OK
: vm_tools::cicerone::AttachUsbToContainerResponse_Status_FAILED);
if (!container_success) {
attach_container_response.set_failure_reason("Expected failure");
}
fake_cicerone_client_->set_attach_usb_to_container_response(
std::move(attach_container_response));
EXPECT_CALL(*this, OnAttach(container_success));
} else {
EXPECT_CALL(*this, OnAttach(vm_success));
}
cros_usb_detector_->AttachUsbDeviceToGuest(
guest_id, guid,
base::BindOnce(&CrosUsbDetectorTest::OnAttach, base::Unretained(this)));
base::RunLoop().RunUntilIdle();
}
void DetachDeviceFromVm(const std::string& vm_name,
const std::string& guid,
bool expected_success) {
cros_usb_detector_->DetachUsbDeviceFromVm(
vm_name, guid,
base::BindOnce(
[](bool expected, bool actual) { EXPECT_EQ(expected, actual); },
expected_success));
base::RunLoop().RunUntilIdle();
}
// The GetSingle..() functions expect only one device is present and may crash
// if there are no devices (we can't use ASSERT_EQ as they return values).
CrosUsbDeviceInfo GetSingleDeviceInfo() const {
auto devices = cros_usb_detector_->GetShareableDevices();
EXPECT_EQ(1U, devices.size());
return devices.front();
}
std::optional<uint8_t> GetSingleGuestPort() const {
EXPECT_EQ(1U, cros_usb_detector_->usb_devices_.size());
return cros_usb_detector_->usb_devices_.begin()->second.guest_port;
}
void AddDisk(const std::string& name,
int bus_number,
int device_number,
bool mounted) {
mock_disk_mount_manager_->CreateDiskEntryForMountDevice(
disks::Disk::Builder()
.SetBusNumber(bus_number)
.SetDeviceNumber(device_number)
.SetDevicePath("/dev/" + name)
.SetMountPath("/mount/" + name)
.SetIsMounted(mounted)
.Build());
if (mounted) {
NotifyMountEvent(name, disks::DiskMountManager::MOUNTING);
}
}
void NotifyMountEvent(const std::string& name,
disks::DiskMountManager::MountEvent event,
MountError mount_error = MountError::kSuccess) {
// In theory we should also clear the mounted flag from the disk, but we
// don't rely on that.
disks::DiskMountManager::MountPoint info{"/dev/" + name, "/mount/" + name,
MountType::kDevice};
mock_disk_mount_manager_->NotifyMountEvent(event, mount_error, info);
}
protected:
std::u16string connection_message(const char* product_name) {
return base::ASCIIToUTF16(base::StringPrintf(
"Open Settings to connect %s to Linux", product_name));
}
std::u16string expected_title() { return u"USB device detected"; }
device::FakeUsbDeviceManager device_manager_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_;
raw_ptr<disks::MockDiskMountManager, DanglingUntriaged>
mock_disk_mount_manager_;
disks::DiskMountManager::Disks disks_;
raw_ptr<ash::FakeCiceroneClient, DanglingUntriaged> fake_cicerone_client_;
raw_ptr<FakeConciergeClient, DanglingUntriaged> fake_concierge_client_;
raw_ptr<FakeVmPluginDispatcherClient, DanglingUntriaged>
fake_vm_plugin_dispatcher_client_;
TestCrosUsbDeviceObserver usb_device_observer_;
std::unique_ptr<CrosUsbDetector> cros_usb_detector_;
std::unique_ptr<crostini::CrostiniTestHelper> crostini_test_helper_;
};
TEST_F(CrosUsbDetectorTest, UsbDeviceAddedAndRemoved) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(expected_title(), notification->title());
EXPECT_EQ(connection_message(kProductName_1), notification->message());
EXPECT_TRUE(notification->delegate());
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Device is removed, so notification should be removed too.
EXPECT_FALSE(display_service_->GetNotification(notification_id));
}
TEST_F(CrosUsbDetectorTest, NotificationShown) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
// Notifications should not be shown if no VMs enabled.
crostini::FakeCrostiniFeatures crostini_features;
crostini_features.set_enabled(false);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Notification should have 1 button when only crostini is enabled.
crostini_features.set_enabled(true);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(notification->buttons().size(), 1u);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Should have 2 buttons when Plugin VM is enabled.
plugin_vm::FakePluginVmFeatures plugin_vm_features;
plugin_vm_features.set_enabled(true);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(notification->buttons().size(), 2u);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Should have 2 buttions when ARCVM is enabled but user disables ARC.
// ARC is disabled by default in test.
arc::ResetArcAllowedCheckForTesting(profile());
auto* command_line = base::CommandLine::ForCurrentProcess();
command_line->InitFromArgv(
{"", "--enable-arcvm", "--arc-availability=officially-supported"});
EXPECT_TRUE(arc::IsArcVmEnabled());
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(notification->buttons().size(), 2u);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Should have 3 buttions when ARCVM is enabled and user enables ARC but the
// feature is disabled.
ASSERT_TRUE(arc::SetArcPlayStoreEnabledForProfile(profile(), true));
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(notification->buttons().size(), 3u);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Now should have 4 buttons when Bruschetta is enabled.
AddContainerToPrefs(profile(), bruschetta::GetBruschettaAlphaId(), {});
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(notification->buttons().size(), 4u);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
}
TEST_F(CrosUsbDetectorTest, NotificationNotShownForEthernetInterface) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0x200, USB_CLASS_COMM, USB_COMM_SUBCLASS_ETHERNET, 0xff, 0x0100, 0, 0, 0,
0, "my cool manufacturer", "my cool ethernet adapter", "SN1337");
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
// Notifications should not be shown if no VMs enabled.
crostini::FakeCrostiniFeatures crostini_features;
crostini_features.set_enabled(false);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Notification should never be shown
crostini_features.set_enabled(true);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
ASSERT_FALSE(notification);
}
TEST_F(CrosUsbDetectorTest, NotificationControlledByPolicy) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
// Notifications should be shown if UsbDetectorNotificationEnabled policy is
// unset.
crostini::FakeCrostiniFeatures crostini_features;
crostini_features.set_enabled(true);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
EXPECT_TRUE(notification);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Notifications should not be shown if UsbDetectorNotificationEnabled policy
// is false.
profile()->GetPrefs()->SetBoolean(prefs::kUsbDetectorNotificationEnabled,
false);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
EXPECT_FALSE(notification);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Notifications should be shown if UsbDetectorNotificationEnabled policy is
// true.
profile()->GetPrefs()->SetBoolean(prefs::kUsbDetectorNotificationEnabled,
true);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
notification = display_service_->GetNotification(notification_id);
EXPECT_TRUE(notification);
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
}
TEST_F(CrosUsbDetectorTest, UsbNotificationClicked) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
notification->delegate()->Click(0, std::nullopt);
base::RunLoop().RunUntilIdle();
EXPECT_GE(fake_concierge_client_->attach_usb_device_call_count(), 1);
// Notification should close.
EXPECT_FALSE(display_service_->GetNotification(notification_id));
}
TEST_F(CrosUsbDetectorTest, UsbDeviceClassAdbAdded) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
const int kAdbClass = 0xff;
const int kAdbSubclass = 0x42;
const int kAdbProtocol = 0x1;
// Adb interface as well as a forbidden interface
scoped_refptr<device::FakeUsbDeviceInfo> device = CreateTestDeviceFromCodes(
/* USB_CLASS_HID */ 0x03,
{InterfaceCodes(kAdbClass, kAdbSubclass, kAdbProtocol),
InterfaceCodes(0x03, 0xff, 0xff)});
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
ASSERT_TRUE(display_service_->GetNotification(notification_id));
// ADB interface wins.
EXPECT_EQ(1U, cros_usb_detector_->GetShareableDevices().size());
}
TEST_F(CrosUsbDetectorTest, UsbDeviceClassWithoutNotificationAdded) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
scoped_refptr<device::FakeUsbDeviceInfo> device =
CreateTestDeviceOfClass(/* USB_CLASS_AUDIO */ 0x01);
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
ASSERT_FALSE(display_service_->GetNotification(notification_id));
EXPECT_EQ(1U, cros_usb_detector_->GetShareableDevices().size());
}
TEST_F(CrosUsbDetectorTest, UsbDeviceWithoutProductNameAddedAndRemoved) {
std::string product_name;
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, product_name, "002");
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(expected_title(), notification->title());
EXPECT_EQ(connection_message("USB device from Google"),
notification->message());
EXPECT_TRUE(notification->delegate());
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Device is removed, so notification should be removed too.
EXPECT_FALSE(display_service_->GetNotification(notification_id));
}
TEST_F(CrosUsbDetectorTest,
UsbDeviceWithoutProductNameOrManufacturerNameAddedAndRemoved) {
std::string product_name;
std::string manufacturer_name;
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, manufacturer_name, product_name, "002");
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id);
ASSERT_TRUE(notification);
EXPECT_EQ(expected_title(), notification->title());
EXPECT_EQ(connection_message(kUnknownProductName), notification->message());
EXPECT_TRUE(notification->delegate());
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
// Device is removed, so notification should be removed too.
EXPECT_FALSE(display_service_->GetNotification(notification_id));
}
TEST_F(CrosUsbDetectorTest, UsbDeviceWasThereBeforeAndThenRemoved) {
// USB device was added before cros_usb_detector was created.
auto device = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device);
base::RunLoop().RunUntilIdle();
std::string notification_id =
CrosUsbDetector::MakeNotificationId(device->guid());
EXPECT_FALSE(display_service_->GetNotification(notification_id));
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
device_manager_.RemoveDevice(device);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id));
}
TEST_F(
CrosUsbDetectorTest,
ThreeUsbDevicesWereThereBeforeAndThenRemovedBeforeUsbDetectorWasCreated) {
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id_1 =
CrosUsbDetector::MakeNotificationId(device_1->guid());
auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
3, 4, kManufacturerName, kProductName_2, "005");
std::string notification_id_2 =
CrosUsbDetector::MakeNotificationId(device_2->guid());
auto device_3 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
6, 7, kManufacturerName, kProductName_3, "008");
std::string notification_id_3 =
CrosUsbDetector::MakeNotificationId(device_3->guid());
// Three usb devices were added and removed before cros_usb_detector was
// created.
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
device_manager_.AddDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.AddDevice(device_3);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
device_manager_.RemoveDevice(device_1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
device_manager_.RemoveDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.RemoveDevice(device_3);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
}
TEST_F(CrosUsbDetectorTest,
ThreeUsbDevicesWereThereBeforeAndThenRemovedAfterUsbDetectorWasCreated) {
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id_1 =
CrosUsbDetector::MakeNotificationId(device_1->guid());
auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
3, 4, kManufacturerName, kProductName_2, "005");
std::string notification_id_2 =
CrosUsbDetector::MakeNotificationId(device_2->guid());
auto device_3 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
6, 7, kManufacturerName, kProductName_3, "008");
std::string notification_id_3 =
CrosUsbDetector::MakeNotificationId(device_3->guid());
// Three usb devices were added before cros_usb_detector was created.
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
device_manager_.AddDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.AddDevice(device_3);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
device_manager_.RemoveDevice(device_1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
device_manager_.RemoveDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.RemoveDevice(device_3);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
}
TEST_F(CrosUsbDetectorTest,
TwoUsbDevicesWereThereBeforeAndThenRemovedAndNewUsbDeviceAdded) {
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id_1 =
CrosUsbDetector::MakeNotificationId(device_1->guid());
auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
3, 4, kManufacturerName, kProductName_2, "005");
std::string notification_id_2 =
CrosUsbDetector::MakeNotificationId(device_2->guid());
// Two usb devices were added before cros_usb_detector was created.
device_manager_.AddDevice(device_1);
device_manager_.AddDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.RemoveDevice(device_1);
device_manager_.RemoveDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.AddDevice(device_2);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification =
display_service_->GetNotification(notification_id_2);
ASSERT_TRUE(notification);
EXPECT_EQ(expected_title(), notification->title());
EXPECT_EQ(connection_message(kProductName_2), notification->message());
EXPECT_TRUE(notification->delegate());
device_manager_.RemoveDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
}
TEST_F(CrosUsbDetectorTest, ThreeUsbDevicesAddedAndRemoved) {
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id_1 =
CrosUsbDetector::MakeNotificationId(device_1->guid());
auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
3, 4, kManufacturerName, kProductName_2, "005");
std::string notification_id_2 =
CrosUsbDetector::MakeNotificationId(device_2->guid());
auto device_3 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
6, 7, kManufacturerName, kProductName_3, "008");
std::string notification_id_3 =
CrosUsbDetector::MakeNotificationId(device_3->guid());
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification_1 =
display_service_->GetNotification(notification_id_1);
ASSERT_TRUE(notification_1);
EXPECT_EQ(expected_title(), notification_1->title());
EXPECT_EQ(connection_message(kProductName_1), notification_1->message());
EXPECT_TRUE(notification_1->delegate());
device_manager_.RemoveDevice(device_1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
device_manager_.AddDevice(device_2);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification_2 =
display_service_->GetNotification(notification_id_2);
ASSERT_TRUE(notification_2);
EXPECT_EQ(expected_title(), notification_2->title());
EXPECT_EQ(connection_message(kProductName_2), notification_2->message());
EXPECT_TRUE(notification_2->delegate());
device_manager_.RemoveDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.AddDevice(device_3);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification_3 =
display_service_->GetNotification(notification_id_3);
ASSERT_TRUE(notification_3);
EXPECT_EQ(expected_title(), notification_3->title());
EXPECT_EQ(connection_message(kProductName_3), notification_3->message());
EXPECT_TRUE(notification_3->delegate());
device_manager_.RemoveDevice(device_3);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
}
TEST_F(CrosUsbDetectorTest, ThreeUsbDeviceAddedAndRemovedDifferentOrder) {
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
std::string notification_id_1 =
CrosUsbDetector::MakeNotificationId(device_1->guid());
auto device_2 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
3, 4, kManufacturerName, kProductName_2, "005");
std::string notification_id_2 =
CrosUsbDetector::MakeNotificationId(device_2->guid());
auto device_3 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
6, 7, kManufacturerName, kProductName_3, "008");
std::string notification_id_3 =
CrosUsbDetector::MakeNotificationId(device_3->guid());
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification_1 =
display_service_->GetNotification(notification_id_1);
ASSERT_TRUE(notification_1);
EXPECT_EQ(expected_title(), notification_1->title());
EXPECT_EQ(connection_message(kProductName_1), notification_1->message());
EXPECT_TRUE(notification_1->delegate());
device_manager_.AddDevice(device_2);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification_2 =
display_service_->GetNotification(notification_id_2);
ASSERT_TRUE(notification_2);
EXPECT_EQ(expected_title(), notification_2->title());
EXPECT_EQ(connection_message(kProductName_2), notification_2->message());
EXPECT_TRUE(notification_2->delegate());
device_manager_.RemoveDevice(device_2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_2));
device_manager_.AddDevice(device_3);
base::RunLoop().RunUntilIdle();
std::optional<message_center::Notification> notification_3 =
display_service_->GetNotification(notification_id_3);
ASSERT_TRUE(notification_3);
EXPECT_EQ(expected_title(), notification_3->title());
EXPECT_EQ(connection_message(kProductName_3), notification_3->message());
EXPECT_TRUE(notification_3->delegate());
device_manager_.RemoveDevice(device_1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_1));
device_manager_.RemoveDevice(device_3);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(display_service_->GetNotification(notification_id_3));
}
TEST_F(CrosUsbDetectorTest, AttachDeviceToVmSetsGuestPort) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(GetSingleGuestPort().has_value());
AttachDeviceToGuest(guest_os::GuestId(crostini::kCrostiniDefaultVmName, ""),
device_info.guid);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
EXPECT_TRUE(GetSingleGuestPort().has_value());
EXPECT_EQ(0U, *GetSingleGuestPort());
}
TEST_F(CrosUsbDetectorTest, AttachingAlreadyAttachedDeviceIsANoOp) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
AttachDeviceToGuest(guest_os::GuestId(crostini::kCrostiniDefaultVmName, ""),
device_info.guid);
cros_usb_detector_->AddUsbDeviceObserver(&usb_device_observer_);
AttachDeviceToGuest(guest_os::GuestId(crostini::kCrostiniDefaultVmName, ""),
device_info.guid);
EXPECT_EQ(0, usb_device_observer_.notify_count());
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
}
TEST_F(CrosUsbDetectorTest, DeviceCanBeAttachedToArcVmWhenCrostiniIsDisabled) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
AttachDeviceToGuest(guest_os::GuestId(arc::kArcVmName, ""), device_info.guid);
base::RunLoop().RunUntilIdle();
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(arc::kArcVmName, device_info.shared_guest_id->vm_name);
}
TEST_F(CrosUsbDetectorTest, SharedDevicesGetAttachedOnStartup) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
cros_usb_detector_->ConnectSharedDevicesOnVmStartup(
crostini::kCrostiniDefaultVmName);
base::RunLoop().RunUntilIdle();
// No device is shared with Crostini, yet.
EXPECT_EQ(0, usb_device_observer_.notify_count());
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
AttachDeviceToGuest(guest_os::GuestId(crostini::kCrostiniDefaultVmName, ""),
device_info.guid);
base::RunLoop().RunUntilIdle();
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
// Concierge::VmStarted signal should trigger connections.
cros_usb_detector_->AddUsbDeviceObserver(&usb_device_observer_);
vm_tools::concierge::VmStartedSignal vm_started_signal;
vm_started_signal.set_name(crostini::kCrostiniDefaultVmName);
fake_concierge_client_->NotifyVmStarted(vm_started_signal);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, usb_device_observer_.notify_count());
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
// VmPluginDispatcherClient::OnVmStateChanged RUNNING should also trigger.
vm_tools::plugin_dispatcher::VmStateChangedSignal vm_state_changed_signal;
vm_state_changed_signal.set_vm_name(crostini::kCrostiniDefaultVmName);
vm_state_changed_signal.set_vm_state(
vm_tools::plugin_dispatcher::VmState::VM_STATE_RUNNING);
fake_vm_plugin_dispatcher_client_->NotifyVmStateChanged(
vm_state_changed_signal);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, usb_device_observer_.notify_count());
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
}
TEST_F(CrosUsbDetectorTest, SwitchDeviceWithAttachSuccess) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
AttachDeviceToGuest(guest_os::GuestId("VM1", ""), device_info.guid,
/*vm_success=*/false);
device_info = GetSingleDeviceInfo();
ASSERT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM1", device_info.shared_guest_id->vm_name);
// Shared but not attached to VM1 -> attached to VM2
AttachDeviceToGuest(guest_os::GuestId("VM2", ""), device_info.guid);
device_info = GetSingleDeviceInfo();
ASSERT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM2", device_info.shared_guest_id->vm_name);
EXPECT_EQ(fake_concierge_client_->detach_usb_device_call_count(), 0);
// Attached to VM2 -> attached to VM3
AttachDeviceToGuest(guest_os::GuestId("VM3", ""), device_info.guid);
device_info = GetSingleDeviceInfo();
ASSERT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM3", device_info.shared_guest_id->vm_name);
EXPECT_GE(fake_concierge_client_->detach_usb_device_call_count(), 1);
}
TEST_F(CrosUsbDetectorTest, SwitchDeviceWithAttachFailure) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
AttachDeviceToGuest(guest_os::GuestId("VM1", ""), device_info.guid);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM1", device_info.shared_guest_id->vm_name);
// Attached to VM1 -> shared but not attached to VM2
AttachDeviceToGuest(guest_os::GuestId("VM2", ""), device_info.guid,
/*vm_success=*/false);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM2", device_info.shared_guest_id->vm_name);
EXPECT_GE(fake_concierge_client_->detach_usb_device_call_count(), 1);
// Shared but not attached to VM2 -> shared but not attached to VM3
AttachDeviceToGuest(guest_os::GuestId("VM3", ""), device_info.guid,
/*vm_success=*/false);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM3", device_info.shared_guest_id->vm_name);
}
TEST_F(CrosUsbDetectorTest, DetachFromDifferentVM) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
AttachDeviceToGuest(guest_os::GuestId("VM1", ""), device_info.guid);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM1", device_info.shared_guest_id->vm_name);
// Device is not attached to VM2, so this will no-op.
DetachDeviceFromVm("VM2", device_info.guid, /*expected_success=*/false);
EXPECT_EQ(fake_concierge_client_->detach_usb_device_call_count(), 0);
EXPECT_EQ("VM1", device_info.shared_guest_id->vm_name);
}
TEST_F(CrosUsbDetectorTest, AttachUnmountFilesystemSuccess) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
device_manager_.CreateAndAddDevice(
0x0200, 0xff, 0xff, 0xff, 0x0100, 1, 2, /*bus_number=*/3,
/*port_number=*/4, kManufacturerName, kProductName_1, "5");
base::RunLoop().RunUntilIdle();
AddDisk("disk1", 3, 4, true);
AddDisk("disk2", 3, 4, /*mounted=*/false);
NotifyMountEvent("disk2", disks::DiskMountManager::MOUNTING,
MountError::kInternalError);
AddDisk("disk3", 3, 5, true);
AddDisk("disk4", 3, 4, true);
AddDisk("disk5", 2, 4, true);
MountCallback callback1;
MountCallback callback4;
EXPECT_CALL(*mock_disk_mount_manager_, UnmountPath("/mount/disk1", _))
.WillOnce(MoveArg<1>(&callback1));
EXPECT_CALL(*mock_disk_mount_manager_, UnmountPath("/mount/disk4", _))
.WillOnce(MoveArg<1>(&callback4));
auto device_info = GetSingleDeviceInfo();
AttachDeviceToGuest(guest_os::GuestId("VM1", ""), device_info.guid);
EXPECT_EQ(fake_concierge_client_->attach_usb_device_call_count(), 0);
// Unmount events would normally be fired by the DiskMountManager.
NotifyMountEvent("disk1", disks::DiskMountManager::UNMOUNTING);
std::move(callback1).Run(MountError::kSuccess);
base::RunLoop().RunUntilIdle();
device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
EXPECT_EQ(fake_concierge_client_->attach_usb_device_call_count(), 0);
// All unmounts must complete before sharing succeeds.
NotifyMountEvent("disk4", disks::DiskMountManager::UNMOUNTING);
std::move(callback4).Run(MountError::kSuccess);
base::RunLoop().RunUntilIdle();
device_info = GetSingleDeviceInfo();
EXPECT_GE(fake_concierge_client_->attach_usb_device_call_count(), 1);
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ("VM1", device_info.shared_guest_id->vm_name);
}
TEST_F(CrosUsbDetectorTest, AttachUnmountFilesystemFailure) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
device_manager_.CreateAndAddDevice(
0x0200, 0xff, 0xff, 0xff, 0x0100, 1, 2, /*bus_number=*/1,
/*port_number=*/5, kManufacturerName, kProductName_1, "5");
base::RunLoop().RunUntilIdle();
AddDisk("disk1", 1, 5, true);
AddDisk("disk2", 1, 5, true);
AddDisk("disk3", 1, 5, true);
MountCallback callback1;
MountCallback callback2;
MountCallback callback3;
EXPECT_CALL(*mock_disk_mount_manager_, UnmountPath("/mount/disk1", _))
.WillOnce(MoveArg<1>(&callback1));
EXPECT_CALL(*mock_disk_mount_manager_, UnmountPath("/mount/disk2", _))
.WillOnce(MoveArg<1>(&callback2));
EXPECT_CALL(*mock_disk_mount_manager_, UnmountPath("/mount/disk3", _))
.WillOnce(MoveArg<1>(&callback3));
// Unmount events would normally be fired by the DiskMountManager.
AttachDeviceToGuest(guest_os::GuestId("VM1", ""), GetSingleDeviceInfo().guid,
/*vm_success=*/false);
NotifyMountEvent("disk1", disks::DiskMountManager::UNMOUNTING);
std::move(callback1).Run(MountError::kSuccess);
std::move(callback2).Run(MountError::kUnknownError);
NotifyMountEvent("disk3", disks::DiskMountManager::UNMOUNTING);
std::move(callback3).Run(MountError::kSuccess);
base::RunLoop().RunUntilIdle();
// AttachDeviceToGuest() verifies CrosUsbDetector correctly calls the
// completion callback, so there's not much to check here.
EXPECT_EQ(fake_concierge_client_->attach_usb_device_call_count(), 0);
}
TEST_F(CrosUsbDetectorTest, ReassignPromptForSharedDevice) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
device_manager_.CreateAndAddDevice(0x1234, 0x5678);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(GetSingleDeviceInfo().prompt_before_sharing);
auto guid = GetSingleDeviceInfo().guid;
AttachDeviceToGuest(guest_os::GuestId("VM1", ""), guid);
EXPECT_TRUE(GetSingleDeviceInfo().prompt_before_sharing);
DetachDeviceFromVm("VM1", guid, /*expected_success=*/true);
EXPECT_FALSE(GetSingleDeviceInfo().prompt_before_sharing);
}
TEST_F(CrosUsbDetectorTest, ReassignPromptForStorageDevice) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
// Disks mounted before the usb device is detected by the CrosUsbDetector
// require a prompt.
AddDisk("disk_early", 1, 5, true);
device_manager_.CreateAndAddDevice(
0x0200, 0xff, 0xff, 0xff, 0x0100, 1, 2, /*bus_number=*/1,
/*port_number=*/5, kManufacturerName, kProductName_1, "5");
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetSingleDeviceInfo().prompt_before_sharing);
NotifyMountEvent("disk_early", disks::DiskMountManager::UNMOUNTING);
EXPECT_FALSE(GetSingleDeviceInfo().prompt_before_sharing);
// A disk which fails to mount shouldn't cause the prompt to be shown.
AddDisk("disk_error", 1, 5, /*mounted=*/false);
NotifyMountEvent("disk_error", disks::DiskMountManager::MOUNTING,
MountError::kInternalError);
EXPECT_FALSE(GetSingleDeviceInfo().prompt_before_sharing);
AddDisk("disk_success", 1, 5, true);
EXPECT_TRUE(GetSingleDeviceInfo().prompt_before_sharing);
}
TEST_F(CrosUsbDetectorTest, AttachDeviceToVmWithContainer) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(GetSingleGuestPort().has_value());
AttachDeviceToGuest(
guest_os::GuestId(crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName),
device_info.guid);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
EXPECT_EQ(crostini::kCrostiniDefaultContainerName,
device_info.shared_guest_id->container_name);
EXPECT_EQ(0U, GetSingleGuestPort());
}
TEST_F(CrosUsbDetectorTest, ReattachDeviceToAnotherContainer) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
AttachDeviceToGuest(
guest_os::GuestId(crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName),
device_info.guid);
AttachDeviceToGuest(guest_os::GuestId(crostini::kCrostiniDefaultVmName,
kCrostiniTestContainerName),
device_info.guid);
device_info = GetSingleDeviceInfo();
EXPECT_TRUE(device_info.shared_guest_id.has_value());
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
EXPECT_EQ(kCrostiniTestContainerName,
device_info.shared_guest_id->container_name);
}
TEST_F(CrosUsbDetectorTest, AttachDeviceOnContainerStartup) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
// Attaching to container fails, as if the container is not started yet.
// But the VM attach should succeed and result in a valid guest_port.
AttachDeviceToGuest(
guest_os::GuestId(crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName),
device_info.guid, /*vm_success=*/true,
/*container_success=*/false);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetSingleGuestPort().has_value());
vm_tools::cicerone::AttachUsbToContainerResponse response;
response.set_status(
vm_tools::cicerone::AttachUsbToContainerResponse_Status_OK);
fake_cicerone_client_->set_attach_usb_to_container_response(
std::move(response));
// cicerone::ContainerStartedSignal triggers container attachment.
cros_usb_detector_->AddUsbDeviceObserver(&usb_device_observer_);
vm_tools::cicerone::ContainerStartedSignal container_started_signal;
container_started_signal.set_vm_name(crostini::kCrostiniDefaultVmName);
container_started_signal.set_container_name(
crostini::kCrostiniDefaultContainerName);
fake_cicerone_client_->NotifyContainerStarted(
std::move(container_started_signal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, usb_device_observer_.notify_count());
device_info = GetSingleDeviceInfo();
EXPECT_EQ(crostini::kCrostiniDefaultVmName,
device_info.shared_guest_id->vm_name);
EXPECT_EQ(crostini::kCrostiniDefaultContainerName,
device_info.shared_guest_id->container_name);
}
TEST_F(CrosUsbDetectorTest, DetachDeviceOnContainerDeleted) {
ConnectToDeviceManager();
base::RunLoop().RunUntilIdle();
auto device_1 = base::MakeRefCounted<device::FakeUsbDeviceInfo>(
0, 1, kManufacturerName, kProductName_1, "002");
device_manager_.AddDevice(device_1);
base::RunLoop().RunUntilIdle();
auto device_info = GetSingleDeviceInfo();
// Attaching to container fails, as if the container is not started yet.
// But the VM attach should succeed and result in a valid guest_port.
AttachDeviceToGuest(
guest_os::GuestId(crostini::kCrostiniDefaultVmName,
crostini::kCrostiniDefaultContainerName),
device_info.guid);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(GetSingleGuestPort().has_value());
// cicerone::LxdContainerDeletedSignal triggers detachment.
cros_usb_detector_->AddUsbDeviceObserver(&usb_device_observer_);
vm_tools::cicerone::LxdContainerDeletedSignal container_deleted_signal;
container_deleted_signal.set_status(
vm_tools::cicerone::LxdContainerDeletedSignal_Status_DELETED);
container_deleted_signal.set_vm_name(crostini::kCrostiniDefaultVmName);
container_deleted_signal.set_container_name(
crostini::kCrostiniDefaultContainerName);
fake_cicerone_client_->NotifyLxdContainerDeleted(
std::move(container_deleted_signal));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, usb_device_observer_.notify_count());
EXPECT_FALSE(GetSingleGuestPort().has_value());
device_info = GetSingleDeviceInfo();
EXPECT_FALSE(device_info.shared_guest_id.has_value());
}
} // namespace ash