// 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/printing/automatic_usb_printer_configurer.h"
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "ash/constants/ash_features.h"
#include "base/containers/flat_set.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/printing/fake_cups_printers_manager.h"
#include "chrome/browser/ash/printing/printers_map.h"
#include "chrome/browser/ash/printing/usb_printer_notification_controller.h"
#include "chromeos/printing/ppd_provider.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ash {
namespace {
using ::chromeos::Printer;
using ::chromeos::PrinterClass;
PrinterDetector::DetectedPrinter CreateUsbPrinter(
const std::string& id,
const std::string& make_and_model) {
PrinterDetector::DetectedPrinter detected;
detected.printer.set_id(id);
detected.printer.SetUri("usb://usb/printer");
detected.printer.set_supports_ippusb(false);
detected.ppd_search_data.make_and_model.push_back(make_and_model);
return detected;
}
PrinterDetector::DetectedPrinter CreateIppUsbPrinter(
const std::string& id,
const std::string& make_and_model) {
PrinterDetector::DetectedPrinter detected;
detected.printer.set_id(id);
detected.printer.SetUri("usb://usb/printer");
detected.printer.set_supports_ippusb(true);
detected.ppd_search_data.make_and_model.push_back(make_and_model);
return detected;
}
} // namespace
class FakeUsbPrinterNotificationController
: public UsbPrinterNotificationController {
public:
FakeUsbPrinterNotificationController() = default;
~FakeUsbPrinterNotificationController() override = default;
void ShowEphemeralNotification(const Printer& printer) override {
open_notifications_.insert(printer.id());
}
void ShowSavedNotification(const Printer& printer) override {
NOTIMPLEMENTED();
}
void ShowConfigurationNotification(const Printer& printer) override {
open_notifications_.insert(printer.id());
}
void RemoveNotification(const std::string& printer_id) override {
open_notifications_.erase(printer_id);
}
bool IsNotificationDisplayed(const std::string& printer_id) const override {
return open_notifications_.contains(printer_id);
}
private:
base::flat_set<std::string> open_notifications_;
};
// Fake PpdProvider backend.
class FakePpdProvider : public chromeos::PpdProvider {
public:
FakePpdProvider() = default;
void SetPpd(const std::string& make_and_model,
const std::string& effective_make_and_model) {
ppds_[make_and_model] = effective_make_and_model;
}
void ResolvePpdReference(const chromeos::PrinterSearchData& search_data,
ResolvePpdReferenceCallback cb) override {
chromeos::PpdProvider::CallbackResultCode code =
chromeos::PpdProvider::NOT_FOUND;
Printer::PpdReference ret;
for (const std::string& make_and_model : search_data.make_and_model) {
auto it = ppds_.find(make_and_model);
if (it != ppds_.end()) {
code = chromeos::PpdProvider::SUCCESS;
ret.effective_make_and_model = it->second;
break;
}
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(cb), code, ret, /*usb_manufacturer=*/""));
}
void ResolvePpd(const chromeos::Printer::PpdReference& reference,
ResolvePpdCallback cb) override {
for (auto kv : ppds_) {
if (kv.second == reference.effective_make_and_model) {
std::move(cb).Run(chromeos::PpdProvider::CallbackResultCode::SUCCESS,
"ppd content");
return;
}
}
std::move(cb).Run(chromeos::PpdProvider::CallbackResultCode::NOT_FOUND, "");
}
// These methods are not used by AutomaticUsbPrinterConfigurer.
void ResolveManufacturers(ResolveManufacturersCallback cb) override {}
void ResolvePrinters(const std::string& manufacturer,
ResolvePrintersCallback cb) override {}
void ResolvePpdLicense(std::string_view effective_make_and_model,
ResolvePpdLicenseCallback cb) override {}
void ReverseLookup(const std::string& effective_make_and_model,
ReverseLookupCallback cb) override {}
private:
~FakePpdProvider() override {}
base::flat_map<std::string, std::string> ppds_;
};
class AutomaticUsbPrinterConfigurerTest : public testing::TestWithParam<bool> {
public:
AutomaticUsbPrinterConfigurerTest() {
if (GetParam()) {
scoped_feature_list_.InitWithFeatures(
{features::kIppFirstSetupForUsbPrinters}, {});
} else {
scoped_feature_list_.InitWithFeatures(
{}, {features::kIppFirstSetupForUsbPrinters});
}
}
AutomaticUsbPrinterConfigurerTest(const AutomaticUsbPrinterConfigurerTest&) =
delete;
AutomaticUsbPrinterConfigurerTest& operator=(
const AutomaticUsbPrinterConfigurerTest&) = delete;
~AutomaticUsbPrinterConfigurerTest() override = default;
protected:
base::test::ScopedFeatureList scoped_feature_list_;
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<FakeCupsPrintersManager> fake_installation_manager_ =
std::make_unique<FakeCupsPrintersManager>();
std::unique_ptr<FakeUsbPrinterNotificationController>
fake_notification_controller_ =
std::make_unique<FakeUsbPrinterNotificationController>();
scoped_refptr<FakePpdProvider> fake_ppd_provider_ =
base::MakeRefCounted<FakePpdProvider>();
std::unique_ptr<AutomaticUsbPrinterConfigurer> auto_usb_printer_configurer_ =
std::make_unique<AutomaticUsbPrinterConfigurer>(
fake_installation_manager_.get(),
fake_notification_controller_.get(),
fake_ppd_provider_.get(),
base::DoNothing());
};
TEST_P(AutomaticUsbPrinterConfigurerTest,
AutoUsbPrinterInstalledAutomatically) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateUsbPrinter(printer_id, "make-and-model");
// Adding a USB printer should result in the printer becoming
// configured and getting marked as installed.
fake_ppd_provider_->SetPpd("make-and-model", "effective-make-and-model");
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(fake_installation_manager_->IsPrinterInstalled(printer.printer));
ASSERT_TRUE(auto_usb_printer_configurer_->ConfiguredPrintersIds().contains(
printer_id));
const chromeos::Printer::PpdReference& ppd_ref =
auto_usb_printer_configurer_->Printer(printer_id).ppd_reference();
EXPECT_EQ(ppd_ref.effective_make_and_model, "effective-make-and-model");
EXPECT_TRUE(ppd_ref.user_supplied_ppd_url.empty());
EXPECT_FALSE(ppd_ref.autoconf);
}
TEST_P(AutomaticUsbPrinterConfigurerTest,
AutoIppUsbPrinterInstalledAutomatically) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateIppUsbPrinter(printer_id, "make-and-model");
// Adding an automatic IPPUSB printer should result in the printer becoming
// configured and getting marked as installed.
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(fake_installation_manager_->IsPrinterInstalled(printer.printer));
ASSERT_TRUE(auto_usb_printer_configurer_->ConfiguredPrintersIds().contains(
printer_id));
const chromeos::Printer::PpdReference& ppd_ref =
auto_usb_printer_configurer_->Printer(printer_id).ppd_reference();
EXPECT_TRUE(ppd_ref.effective_make_and_model.empty());
EXPECT_TRUE(ppd_ref.user_supplied_ppd_url.empty());
EXPECT_TRUE(ppd_ref.autoconf);
}
TEST_P(AutomaticUsbPrinterConfigurerTest, DiscoveredUsbPrinterNotInstalled) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateUsbPrinter(printer_id, "make-and-model");
// Adding a discovered USB printer should not result in the printer getting
// installed.
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_FALSE(fake_installation_manager_->IsPrinterInstalled(printer.printer));
}
TEST_P(AutomaticUsbPrinterConfigurerTest, UsbPrinterAddedToSet) {
const std::string automatic_printer_id = "auto_id";
const PrinterDetector::DetectedPrinter automatic_printer =
CreateIppUsbPrinter(automatic_printer_id, "");
const std::string discovered_printer_id = "disco_id";
const PrinterDetector::DetectedPrinter discovered_printer =
CreateUsbPrinter(discovered_printer_id, "");
// Adding an IPP USB printer should result in the printer getting added
// to the list of configured printers.
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters(
{automatic_printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(auto_usb_printer_configurer_->ConfiguredPrintersIds().contains(
automatic_printer_id));
EXPECT_TRUE(auto_usb_printer_configurer_->UnconfiguredPrintersIds().empty());
// Adding a non-IPP USB printer should result in the printer getting added
// to the list of unconfigured printers (no PPDs are available).
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters(
{automatic_printer, discovered_printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(auto_usb_printer_configurer_->ConfiguredPrintersIds().contains(
automatic_printer_id));
EXPECT_TRUE(auto_usb_printer_configurer_->UnconfiguredPrintersIds().contains(
discovered_printer_id));
EXPECT_EQ(1u, auto_usb_printer_configurer_->ConfiguredPrintersIds().size());
EXPECT_EQ(1u, auto_usb_printer_configurer_->UnconfiguredPrintersIds().size());
// Removing the non-IPP printer should result in the printer getting
// removed from the list of unconfigured printers.
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters(
{automatic_printer});
task_environment_.RunUntilIdle();
EXPECT_EQ(0u, auto_usb_printer_configurer_->UnconfiguredPrintersIds().size());
// Removing the IPP printer should result in the printer getting
// removed from the list of configured printers.
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({});
task_environment_.RunUntilIdle();
EXPECT_EQ(0u, auto_usb_printer_configurer_->ConfiguredPrintersIds().size());
}
TEST_P(AutomaticUsbPrinterConfigurerTest, NotificationOpenedForNewAutomatic) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateIppUsbPrinter(printer_id, "");
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(
fake_notification_controller_->IsNotificationDisplayed(printer_id));
}
TEST_P(AutomaticUsbPrinterConfigurerTest, NotificationClosed) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateIppUsbPrinter(printer_id, "");
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(
fake_notification_controller_->IsNotificationDisplayed(printer_id));
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({});
task_environment_.RunUntilIdle();
EXPECT_FALSE(
fake_notification_controller_->IsNotificationDisplayed(printer_id));
}
TEST_P(AutomaticUsbPrinterConfigurerTest, NotificationOpenedForNewDiscovered) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateUsbPrinter(printer_id, "");
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(
fake_notification_controller_->IsNotificationDisplayed(printer_id));
}
TEST_P(AutomaticUsbPrinterConfigurerTest, IppUsbPrinterWithPpdDependsOnFlag) {
const std::string printer_id = "id";
const PrinterDetector::DetectedPrinter printer =
CreateIppUsbPrinter(printer_id, "Make & Model");
fake_ppd_provider_->SetPpd("Make & Model", "make-and-model");
auto_usb_printer_configurer_->UpdateListOfConnectedPrinters({printer});
task_environment_.RunUntilIdle();
EXPECT_TRUE(fake_installation_manager_->IsPrinterInstalled(printer.printer));
ASSERT_TRUE(auto_usb_printer_configurer_->ConfiguredPrintersIds().contains(
printer_id));
const chromeos::Printer::PpdReference& ppd_ref =
auto_usb_printer_configurer_->Printer(printer_id).ppd_reference();
EXPECT_TRUE(ppd_ref.user_supplied_ppd_url.empty());
if (GetParam()) {
// The printer should be set up as IPP Everywhere printer.
EXPECT_TRUE(ppd_ref.effective_make_and_model.empty());
EXPECT_TRUE(ppd_ref.autoconf);
} else {
// The printer should be set up via PPD file.
EXPECT_EQ(ppd_ref.effective_make_and_model, "make-and-model");
EXPECT_FALSE(ppd_ref.autoconf);
}
}
INSTANTIATE_TEST_SUITE_P(IppFirstSetupForUsbPrinters,
AutomaticUsbPrinterConfigurerTest,
testing::Values(false, true));
} // namespace ash