#include "chrome/browser/ash/printing/printer_setup_util.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "chrome/browser/ash/printing/cups_printers_manager.h"
#include "chrome/browser/ash/printing/cups_printers_manager_factory.h"
#include "chrome/browser/browser_process.h"
#include "chromeos/printing/printer_configuration.h"
#include "components/crash/core/common/crash_keys.h"
#include "content/public/browser/browser_thread.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom.h"
#include "printing/printing_features.h"
#if BUILDFLAG(ENABLE_OOP_PRINTING)
#include "chrome/browser/printing/oop_features.h"
#include "chrome/browser/printing/print_backend_service_manager.h"
#include "chrome/services/printing/public/mojom/print_backend_service.mojom.h"
#endif
namespace ash {
namespace printing {
namespace {
void LogPrinterSetup(const chromeos::Printer& printer,
PrinterSetupResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::UmaHistogramEnumeration(
printer.IsZeroconf()
? "Printing.CUPS.ZeroconfPrinterSetupResult.PrintPreview"
: "Printing.CUPS.PrinterSetupResult.PrintPreview",
result, PrinterSetupResult::kMaxValue);
switch (result) {
case PrinterSetupResult::kSuccess: {
VLOG(1) << "Printer setup successful for " << printer.id()
<< " fetching properties";
if (printer.IsUsbProtocol()) {
PrinterConfigurer::RecordUsbPrinterSetupSource(
UsbPrinterSetupSource::kPrintPreview);
}
return;
}
case PrinterSetupResult::kPrinterUnreachable:
case PrinterSetupResult::kPrinterSentWrongResponse:
case PrinterSetupResult::kPpdNotFound:
case PrinterSetupResult::kPpdUnretrievable:
LOG(WARNING) << printer.id() << ": printer setup failed for "
<< printer.make_and_model() << ": "
<< ResultCodeToMessage(result);
break;
case PrinterSetupResult::kFatalError:
case PrinterSetupResult::kDbusError:
case PrinterSetupResult::kNativePrintersNotAllowed:
case PrinterSetupResult::kPpdTooLarge:
case PrinterSetupResult::kInvalidPpd:
case PrinterSetupResult::kIoError:
case PrinterSetupResult::kMemoryAllocationError:
case PrinterSetupResult::kBadUri:
case PrinterSetupResult::kDbusNoReply:
case PrinterSetupResult::kDbusTimeout:
case PrinterSetupResult::kManualSetupRequired:
case PrinterSetupResult::kPrinterRemoved:
case PrinterSetupResult::kPrintscanmgrDbusNoReply:
case PrinterSetupResult::kDebugdDbusNoReply:
LOG(ERROR) << printer.id() << ": printer setup failed for "
<< printer.make_and_model() << ": "
<< ResultCodeToMessage(result);
break;
case PrinterSetupResult::kInvalidPrinterUpdate:
case PrinterSetupResult::kEditSuccess:
case PrinterSetupResult::kPrinterIsNotAutoconfigurable:
case PrinterSetupResult::kComponentUnavailable:
LOG(ERROR) << printer.id() << ": unexpected error in printer setup for "
<< printer.make_and_model() << ": "
<< ResultCodeToMessage(result);
break;
}
}
std::optional<::printing::PrinterSemanticCapsAndDefaults>
FetchCapabilitiesOnBlockingTaskRunner(const std::string& printer_id,
const std::string& locale) {
auto print_backend = ::printing::PrintBackend::CreateInstance(locale);
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
VLOG(1) << "Get printer capabilities start for " << printer_id;
crash_keys::ScopedPrinterInfo crash_key(
printer_id, print_backend->GetPrinterDriverInfo(printer_id));
auto caps = std::make_optional<::printing::PrinterSemanticCapsAndDefaults>();
if (print_backend->GetPrinterSemanticCapsAndDefaults(printer_id, &*caps) !=
::printing::mojom::ResultCode::kSuccess) {
LOG(WARNING) << "Failed to get capabilities for " << printer_id;
return std::nullopt;
}
return caps;
}
#if BUILDFLAG(ENABLE_OOP_PRINTING)
void CapabilitiesFetchedFromService(
::printing::PrintBackendServiceManager::ClientId client_id,
const std::string& printer_id,
bool elevated_privileges,
GetPrinterCapabilitiesCallback cb,
::printing::mojom::PrintBackendService::
GetPrinterSemanticCapsAndDefaultsResult printer_caps) {
if (!printer_caps.has_value()) {
LOG(WARNING) << "Failure fetching printer capabilities from service for "
<< printer_id << " - error " << printer_caps.error();
if (printer_caps.error() == ::printing::mojom::ResultCode::kAccessDenied &&
!elevated_privileges) {
::printing::PrintBackendServiceManager& service_mgr =
::printing::PrintBackendServiceManager::GetInstance();
service_mgr.SetPrinterDriverFoundToRequireElevatedPrivilege(printer_id);
service_mgr.GetPrinterSemanticCapsAndDefaults(
printer_id,
base::BindOnce(&CapabilitiesFetchedFromService, client_id, printer_id,
true, std::move(cb)));
return;
}
::printing::PrintBackendServiceManager::GetInstance().UnregisterClient(
client_id);
std::move(cb).Run(std::nullopt);
return;
}
::printing::PrintBackendServiceManager::GetInstance().UnregisterClient(
client_id);
VLOG(1) << "Successfully received printer capabilities from service for "
<< printer_id;
std::move(cb).Run(printer_caps.value());
}
#endif
void FetchCapabilities(const std::string& printer_id,
GetPrinterCapabilitiesCallback cb) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
#if BUILDFLAG(ENABLE_OOP_PRINTING)
if (::printing::IsOopPrintingEnabled()) {
VLOG(1) << "Fetching printer capabilities via service";
::printing::PrintBackendServiceManager& service_mgr =
::printing::PrintBackendServiceManager::GetInstance();
::printing::PrintBackendServiceManager::ClientId client_id =
service_mgr.RegisterQueryClient();
service_mgr.GetPrinterSemanticCapsAndDefaults(
printer_id,
base::BindOnce(&CapabilitiesFetchedFromService, client_id, printer_id,
service_mgr.PrinterDriverFoundToRequireElevatedPrivilege(
printer_id),
std::move(cb)));
return;
}
#endif
VLOG(1) << "Fetching printer capabilities in-process";
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
base::BindOnce(FetchCapabilitiesOnBlockingTaskRunner, printer_id,
g_browser_process->GetApplicationLocale()),
std::move(cb));
}
void OnPrinterInstalled(
CupsPrintersManager* printers_manager,
const chromeos::Printer& printer,
base::OnceCallback<void(
const std::optional<::printing::PrinterSemanticCapsAndDefaults>&)> cb,
PrinterSetupResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
LogPrinterSetup(printer, result);
if (result != PrinterSetupResult::kSuccess) {
std::move(cb).Run(std::nullopt);
return;
}
FetchCapabilities(printer.id(), std::move(cb));
}
}
void SetUpPrinter(CupsPrintersManager* printers_manager,
const chromeos::Printer& printer,
GetPrinterCapabilitiesCallback cb) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::UmaHistogramEnumeration("Printing.CUPS.ProtocolUsed",
printer.GetProtocol(),
chromeos::Printer::kProtocolMax);
if (printers_manager->IsPrinterInstalled(printer)) {
FetchCapabilities(printer.id(), std::move(cb));
return;
}
printers_manager->SetUpPrinter(
printer, true,
base::BindOnce(OnPrinterInstalled, printers_manager, printer,
std::move(cb)));
}
}
}