chromium/chrome/browser/ash/printing/cups_printers_manager.cc

// Copyright 2017 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/cups_printers_manager.h"

#include <map>
#include <optional>
#include <utility>

#include "ash/public/cpp/network_config_service.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "base/scoped_observation.h"
#include "base/sequence_checker.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/timer/elapsed_timer.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/printing/automatic_usb_printer_configurer.h"
#include "chrome/browser/ash/printing/cups_printer_status_creator.h"
#include "chrome/browser/ash/printing/enterprise/enterprise_printers_provider.h"
#include "chrome/browser/ash/printing/enterprise/print_servers_policy_provider.h"
#include "chrome/browser/ash/printing/enterprise/print_servers_provider.h"
#include "chrome/browser/ash/printing/oauth2/client_ids_database.h"
#include "chrome/browser/ash/printing/ppd_provider_factory.h"
#include "chrome/browser/ash/printing/ppd_resolution_tracker.h"
#include "chrome/browser/ash/printing/printer_configurer.h"
#include "chrome/browser/ash/printing/printer_event_tracker.h"
#include "chrome/browser/ash/printing/printer_event_tracker_factory.h"
#include "chrome/browser/ash/printing/printer_info.h"
#include "chrome/browser/ash/printing/printers_map.h"
#include "chrome/browser/ash/printing/server_printers_provider.h"
#include "chrome/browser/ash/printing/synced_printers_manager.h"
#include "chrome/browser/ash/printing/synced_printers_manager_factory.h"
#include "chrome/browser/ash/printing/usb_printer_detector.h"
#include "chrome/browser/ash/printing/usb_printer_notification_controller.h"
#include "chrome/browser/ash/printing/zeroconf_printer_detector.h"
#include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h"
#include "chrome/browser/printing/print_preview_sticky_settings.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/components/dbus/printscanmgr/printscanmgr_client.h"
#include "chromeos/ash/components/settings/cros_settings.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/printing_constants.h"
#include "chromeos/printing/uri.h"
#include "chromeos/services/network_config/public/cpp/cros_network_config_observer.h"
#include "components/device_event_log/device_event_log.h"
#include "components/policy/policy_constants.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_member.h"
#include "components/prefs/pref_service.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "printing/printer_query_result.h"

namespace ash {

constexpr base::TimeDelta kMetricsDelayTimerInterval = base::Minutes(1);
constexpr base::TimeDelta kMaxPrinterStatusPollingTime = base::Minutes(5);

enum class PollingIntervalLength {
  kShort = 0,
  kMedium = 1,
  kLong = 2,
  kMaxValue = kLong,
};

// Maps a PollingIntervalLength to a pair of numbers representing a range of
// seconds. The polling timer delay is chosen by randomly choosing a number
// between this range. Unreachable printers are more likely see a status change
// (by turning on or connecting to the network) so they should be queried more
// often.
constexpr auto kUnreachableStatePollingIntervalMap =
    base::MakeFixedFlatMap<PollingIntervalLength, std::pair<int, int>>(
        {{PollingIntervalLength::kShort, {10, 15}},
         {PollingIntervalLength::kMedium, {25, 30}},
         {PollingIntervalLength::kLong, {45, 60}}});
constexpr auto kGoodStatePollingIntervalMap =
    base::MakeFixedFlatMap<PollingIntervalLength, std::pair<int, int>>(
        {{PollingIntervalLength::kShort, {25, 30}},
         {PollingIntervalLength::kMedium, {60, 80}},
         {PollingIntervalLength::kLong, {60, 80}}});

bool IsIppUri(const chromeos::Uri& uri) {
  return (uri.GetScheme() == chromeos::kIppScheme ||
          uri.GetScheme() == chromeos::kIppsScheme);
}

// TODO(b/192467856) Remove this metric gathering by M99
void SendScannerCountToUMA(std::unique_ptr<ZeroconfScannerDetector> detector) {
  if (detector == nullptr) {
    PRINTER_LOG(DEBUG) << "SendScannerCountToUMA detector was null";
    return;
  }
  const uint16_t num_scanners = detector->GetScanners().size();
  base::UmaHistogramCounts100("Scanning.NumDetectedScannersAtLogin",
                              num_scanners);
}

namespace {

using ::chromeos::CupsPrinterStatus;
using ::chromeos::PpdProvider;
using ::chromeos::Printer;
using ::chromeos::PrinterClass;
using ::printing::PrinterQueryResult;

void OnRemovedPrinter(
    std::optional<printscanmgr::CupsRemovePrinterResponse> response) {
  if (!response) {
    PRINTER_LOG(DEBUG) << "No response to remove printer request.";
    return;
  }

  if (response->result()) {
    PRINTER_LOG(DEBUG) << "Printer removal succeeded.";
  } else {
    PRINTER_LOG(DEBUG) << "Printer removal failed.";
  }
}

class CupsPrintersManagerImpl
    : public CupsPrintersManager,
      public EnterprisePrintersProvider::Observer,
      public PrintServersManager::Observer,
      public SyncedPrintersManager::Observer,
      public chromeos::network_config::CrosNetworkConfigObserver {
 public:
  // Identifiers for each of the underlying PrinterDetectors this
  // class observes.
  enum DetectorIds { kUsbDetector, kZeroconfDetector, kPrintServerDetector };

  CupsPrintersManagerImpl(
      SyncedPrintersManager* synced_printers_manager,
      std::unique_ptr<PrinterDetector> usb_detector,
      std::unique_ptr<PrinterDetector> zeroconf_detector,
      scoped_refptr<PpdProvider> ppd_provider,
      DlcserviceClient* dlc_service_client,
      std::unique_ptr<UsbPrinterNotificationController>
          usb_notification_controller,
      std::unique_ptr<PrintServersManager> print_servers_manager,
      std::unique_ptr<EnterprisePrintersProvider> enterprise_printers_provider,
      PrinterEventTracker* event_tracker,
      PrefService* pref_service)
      : synced_printers_manager_(synced_printers_manager),
        usb_detector_(std::move(usb_detector)),
        zeroconf_detector_(std::move(zeroconf_detector)),
        ppd_provider_(std::move(ppd_provider)),
        dlc_service_client_(dlc_service_client),
        usb_notification_controller_(std::move(usb_notification_controller)),
        print_servers_manager_(std::move(print_servers_manager)),
        enterprise_printers_provider_(std::move(enterprise_printers_provider)),
        event_tracker_(event_tracker),
        nearby_printers_metric_delay_timer_(
            FROM_HERE,
            kMetricsDelayTimerInterval,
            /*receiver=*/this,
            &CupsPrintersManagerImpl::RecordTotalNearbyNetworkPrinterCounts) {
    auto_usb_printer_configurer_ =
        std::make_unique<AutomaticUsbPrinterConfigurer>(
            this, usb_notification_controller_.get(), ppd_provider_.get(),
            base::BindRepeating(&CupsPrintersManagerImpl::OnUsbPrinterSetupDone,
                                weak_ptr_factory_.GetWeakPtr()));

    GetNetworkConfigService(
        remote_cros_network_config_.BindNewPipeAndPassReceiver());

    remote_cros_network_config_->AddObserver(
        cros_network_config_observer_receiver_.BindNewPipeAndPassRemote());

    // Prime the printer cache with the saved printers.
    printers_.ReplacePrintersInClass(
        PrinterClass::kSaved, synced_printers_manager_->GetSavedPrinters());
    synced_printers_manager_observation_.Observe(
        synced_printers_manager_.get());

    // Prime the printer cache with the enterprise printers (observer called
    // immediately).
    enterprise_printers_provider_observation_.Observe(
        enterprise_printers_provider_.get());

    // Callbacks may ensue immediately when the observer proxies are set up, so
    // these instantiations must come after everything else is initialized.
    usb_detector_->RegisterPrintersFoundCallback(
        base::BindRepeating(&CupsPrintersManagerImpl::OnPrintersFound,
                            weak_ptr_factory_.GetWeakPtr(), kUsbDetector));
    OnPrintersFound(kUsbDetector, usb_detector_->GetPrinters());

    zeroconf_detector_->RegisterPrintersFoundCallback(
        base::BindRepeating(&CupsPrintersManagerImpl::OnPrintersFound,
                            weak_ptr_factory_.GetWeakPtr(), kZeroconfDetector));
    OnPrintersFound(kZeroconfDetector, zeroconf_detector_->GetPrinters());

    // TODO(b/192467856) Remove this metric gathering by M99
    // Creates a ZeroconfScannerDetector, then logs the number of scanners
    // detected after 5 minutes.
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&SendScannerCountToUMA,
                       ZeroconfScannerDetector::Create()),
        base::Minutes(5));

    print_servers_manager_->AddObserver(this);

    user_printers_allowed_.Init(prefs::kUserPrintersAllowed, pref_service);
  }

  ~CupsPrintersManagerImpl() override = default;

  // Public API function.
  std::vector<Printer> GetPrinters(PrinterClass printer_class) const override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (!user_printers_allowed_.GetValue() &&
        printer_class != PrinterClass::kEnterprise) {
      // If printers are disabled then simply return an empty vector.
      LOG(WARNING) << "Attempting to retrieve printers when "
                      "UserPrintersAllowed is set to false";
      return {};
    }

    // Without user data there is not need to filter out non-enterprise or
    // insecure printers so return all the printers in |printer_class|.
    return printers_.Get(printer_class);
  }

  // Public API function.
  void SavePrinter(const Printer& printer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (!user_printers_allowed_.GetValue()) {
      LOG(WARNING) << "SavePrinter() called when "
                      "UserPrintersAllowed is set to false";
      return;
    }
    synced_printers_manager_->UpdateSavedPrinter(printer);
    // Note that we will rebuild our lists when we get the observer
    // callback from |synced_printers_manager_|.
  }

  // Public API function.
  void RemoveSavedPrinter(const std::string& printer_id) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    UninstallPrinter(printer_id);
    auto existing = synced_printers_manager_->GetPrinter(printer_id);
    if (existing) {
      event_tracker_->RecordPrinterRemoved(*existing);
      const Printer::PrinterProtocol protocol = existing->GetProtocol();
      base::UmaHistogramEnumeration("Printing.CUPS.PrinterRemoved", protocol,
                                    Printer::PrinterProtocol::kProtocolMax);
    }
    synced_printers_manager_->RemoveSavedPrinter(printer_id);
    // Note that we will rebuild our lists when we get the observer
    // callback from |synced_printers_manager_|.
  }

  // Public API function.
  void AddObserver(CupsPrintersManager::Observer* observer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    observer_list_.AddObserver(observer);
    if (enterprise_printers_are_ready_) {
      observer->OnEnterprisePrintersInitialized();
    }
  }

  // Public API function.
  void RemoveObserver(CupsPrintersManager::Observer* observer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    observer_list_.RemoveObserver(observer);
  }

  // Public API function.
  void AddLocalPrintersObserver(
      CupsPrintersManager::LocalPrintersObserver* observer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);

    if (!local_printers_observer_list_.HasObserver(observer)) {
      local_printers_observer_list_.AddObserver(observer);
      observer->OnLocalPrintersUpdated();
    }

    // Begin polling printers for printer status for 5 minutes.
    StartPrinterStatusPolling();
  }

  // Public API function.
  void RemoveLocalPrintersObserver(
      CupsPrintersManager::LocalPrintersObserver* observer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    local_printers_observer_list_.RemoveObserver(observer);
  }

  // Public API function.
  bool IsPrinterInstalled(const Printer& printer) const override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    const auto found = installed_printer_fingerprints_.find(printer.id());
    if (found == installed_printer_fingerprints_.end()) {
      return false;
    }

    return found->second == PrinterConfigurer::SetupFingerprint(printer);
  }

  // Public API function.
  std::optional<Printer> GetPrinter(const std::string& id) const override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (!user_printers_allowed_.GetValue()) {
      LOG(WARNING) << "UserPrintersAllowed is disabled - only searching "
                      "enterprise printers";
      return GetEnterprisePrinter(id);
    }

    return printers_.Get(id);
  }

  // SyncedPrintersManager::Observer implementation
  void OnSavedPrintersChanged() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    ResetNearbyPrintersLists();
    printers_.ReplacePrintersInClass(
        PrinterClass::kSaved, synced_printers_manager_->GetSavedPrinters());
    RebuildDetectedLists();
    NotifyObservers({PrinterClass::kSaved});
  }

  // EnterprisePrintersProvider::Observer implementation
  void OnPrintersChanged(bool complete,
                         const std::vector<Printer>& printers) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (complete) {
      printers_.ReplacePrintersInClass(PrinterClass::kEnterprise, printers);
      if (!enterprise_printers_are_ready_) {
        enterprise_printers_are_ready_ = true;
        for (auto& observer : observer_list_) {
          observer.OnEnterprisePrintersInitialized();
        }
      }
    }
    NotifyObservers({PrinterClass::kEnterprise});
  }

  // CrosNetworkConfigObserver implementation.
  void OnActiveNetworksChanged(
      std::vector<chromeos::network_config::mojom::NetworkStatePropertiesPtr>
          networks) override {
    if (!HasNetworkDisconnected(networks)) {
      // We only update the discovered list if we disconnected from our previous
      // default network.
      return;
    }

    PRINTER_LOG(DEBUG) << "Network change.  Refresh printers list.";

    // Clear the network detected printers when the active network changes.
    // This ensures that connecting to a new network will give us only newly
    // detected printers.
    ClearNetworkDetectedPrinters();

    // Notify observers that the printer list has changed.
    RebuildDetectedLists();
  }

  // Callback for PrinterDetectors.
  void OnPrintersFound(
      int detector_id,
      const std::vector<PrinterDetector::DetectedPrinter>& printers) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    switch (detector_id) {
      case kUsbDetector:
        usb_detections_ = printers;
        break;
      case kZeroconfDetector:
        zeroconf_detections_ = printers;
        // Start timer for recording the # of nearby printers.
        nearby_printers_metric_delay_timer_.Reset();
        break;
      case kPrintServerDetector:
        servers_detections_ = printers;
        break;
    }
    RebuildDetectedLists();
  }

  // Callback for PrintServersManager.
  void OnServerPrintersChanged(
      const std::vector<PrinterDetector::DetectedPrinter>& printers) override {
    OnPrintersFound(kPrintServerDetector, printers);
  }

  void SetUpPrinter(const chromeos::Printer& printer,
                    bool is_automatic_installation,
                    PrinterSetupCallback callback) override {
    // Check if the printer is currently set up.
    if (IsPrinterInstalled(printer)) {
      std::move(callback).Run(PrinterSetupResult::kSuccess);
      return;
    }

    const std::string id = printer.id();
    const std::string fingerprint =
        PrinterConfigurer::SetupFingerprint(printer);

    // Add `callback` to an existing record or creates a new record with
    // an empty fingerprint and a single callback.
    printers_being_setup_[id].callbacks.push_back(std::move(callback));

    // If the record was just created or the fingerprint does not match the
    // previous one, we have to initialize/overwrite the rest of the fields and
    // start/restart the setup process.
    if (printers_being_setup_[id].fingerprint != fingerprint) {
      printers_being_setup_[id].configurer =
          PrinterConfigurer::Create(ppd_provider_, dlc_service_client_);
      printers_being_setup_[id].fingerprint = fingerprint;
      printers_being_setup_[id].configurer->SetUpPrinterInCups(
          printer,
          base::BindOnce(&CupsPrintersManagerImpl::OnPrinterSetupResult,
                         weak_ptr_factory_.GetWeakPtr(), id,
                         is_automatic_installation));
    }
  }

  void UninstallPrinter(const std::string& printer_id) override {
    // Uninstall printer if installed completely.
    if (installed_printer_fingerprints_.erase(printer_id)) {
      // The printer was present in `installed_printer_fingerprints_`.
      printscanmgr::CupsRemovePrinterRequest request;
      request.set_name(printer_id);
      PrintscanmgrClient::Get()->CupsRemovePrinter(
          request, base::BindOnce(&OnRemovedPrinter), base::DoNothing());
      return;
    }

    // If the printer is being installed now, stop the process.
    std::vector<PrinterSetupCallback> callbacks;
    if (printers_being_setup_.contains(printer_id)) {
      callbacks = std::move(printers_being_setup_[printer_id].callbacks);
      printers_being_setup_.erase(printer_id);
    }
    for (auto& callback : callbacks) {
      std::move(callback).Run(PrinterSetupResult::kPrinterRemoved);
    }
  }

  // Resets the overall polling timer then executes the first round of printer
  // status queries for good and unreachable printers.
  void StartPrinterStatusPolling() {
    printer_status_polling_total_duration_timer_ =
        std::make_unique<base::ElapsedTimer>();
    OnPrinterStatusTimerElapsed(/*for_unreachable_printers=*/true);
    OnPrinterStatusTimerElapsed(/*for_unreachable_printers=*/false);
  }

  // Determines if a printer is unreachable based on it's previously acquired
  // printer status.
  bool IsPrinterUnreachable(const chromeos::Printer& printer) {
    const auto printer_status = printer.printer_status();
    for (const auto& reason : printer.printer_status().GetStatusReasons()) {
      if (reason.GetReason() == CupsPrinterStatus::CupsPrinterStatusReason::
                                    Reason::kPrinterUnreachable) {
        return true;
      }
    }

    return false;
  }

  // Returns the next polling delay in seconds based on the state of the
  // printers, the # of printers being queried, and total polling time elapsed.
  int GetPrinterStatusPollingDelay(bool for_unreachable_printers,
                                   int printers_queried) {
    // After polling for 2 minutes the printers' statuses are less likely to
    // change so increase the polling delay.
    const base::TimeDelta kLongDuration = base::Minutes(2);
    // If there are large number of printers to query, increase the polling
    // delay to reduce the overall use of network bandwidth.
    const int kMaxPrinters = 3;

    // Unreachable printers are more likely to have their status changed (by
    // being turned on and connecting to the network) so they should be
    // queried more often.
    const auto& polling_intervals = for_unreachable_printers
                                        ? kUnreachableStatePollingIntervalMap
                                        : kGoodStatePollingIntervalMap;
    std::pair<int, int> interval;
    if (printer_status_polling_total_duration_timer_->Elapsed() >
        kLongDuration) {
      interval = polling_intervals.find(PollingIntervalLength::kLong)->second;
    } else if (printers_queried > kMaxPrinters) {
      interval = polling_intervals.find(PollingIntervalLength::kMedium)->second;
    } else {
      interval = polling_intervals.find(PollingIntervalLength::kShort)->second;
    }

    // Choose a random int between the selected interval.
    return interval.first + (rand() % (interval.second - interval.first + 1));
  }

  // Starts printer status requests for all Saved and recently used printers
  // then queues the next round of requests if the overall timer hasn't elapsed.
  // `for_unreachable_printers` determines when set of polling intervals to use.
  void OnPrinterStatusTimerElapsed(bool for_unreachable_printers) {
    std::vector<std::string> recently_used_printers;
    ::printing::PrintPreviewStickySettings* sticky_settings =
        ::printing::PrintPreviewStickySettings::GetInstance();
    if (sticky_settings) {
      recently_used_printers = sticky_settings->GetRecentlyUsedPrinters();
    }

    int printers_queried = 0;
    const auto printers = printers_.Get();
    for (const auto& printer : printers) {
      // Ensure the correct set of printers is being queried.
      if (IsPrinterUnreachable(printer) != for_unreachable_printers) {
        continue;
      }

      // Query every printer that is either saved or recently used.
      if (printers_.IsPrinterInClass(chromeos::PrinterClass::kSaved,
                                     printer.id()) ||
          base::Contains(recently_used_printers, printer.id())) {
        FetchPrinterStatus(printer.id(),
                           /*PrinterStatusCallback=*/base::DoNothing());
        ++printers_queried;
      }
    }

    // Only restart requests when the 5 minute timer hasn't elapsed.
    if (printer_status_polling_total_duration_timer_->Elapsed() <
        kMaxPrinterStatusPollingTime) {
      auto& timer = for_unreachable_printers
                        ? printer_status_unreachable_state_timer_
                        : printer_status_good_state_timer_;
      timer.Start(
          FROM_HERE,
          base::Seconds(GetPrinterStatusPollingDelay(for_unreachable_printers,
                                                     printers_queried)),
          base::BindOnce(&CupsPrintersManagerImpl::OnPrinterStatusTimerElapsed,
                         weak_ptr_factory_.GetWeakPtr(),
                         for_unreachable_printers));
    }
  }

  void FetchPrinterStatus(const std::string& printer_id,
                          PrinterStatusCallback cb) override {
    std::optional<Printer> printer = GetPrinter(printer_id);
    if (!printer) {
      PRINTER_LOG(ERROR) << "Unable to complete printer status request. "
                         << "Printer not found. Printer id: " << printer_id;
      CupsPrinterStatus printer_status(printer_id);
      printer_status.AddStatusReason(
          CupsPrinterStatus::CupsPrinterStatusReason::Reason::
              kPrinterUnreachable,
          CupsPrinterStatus::CupsPrinterStatusReason::Severity::kError);
      SendPrinterStatus(printer_status, std::move(cb),
                        /*notify_observers=*/false);
      return;
    }

    // For USB printers, return NO ERROR if the printer is connected or PRINTER
    // UNREACHABLE if the printer is disconnected.
    if (printer->IsUsbProtocol()) {
      CupsPrinterStatus printer_status(printer_id);
      if (FindDetectedPrinter(printer_id)) {
        printer_status.AddStatusReason(
            CupsPrinterStatus::CupsPrinterStatusReason::Reason::kNoError,
            CupsPrinterStatus::CupsPrinterStatusReason::Severity::
                kUnknownSeverity);
      } else {
        printer_status.AddStatusReason(
            CupsPrinterStatus::CupsPrinterStatusReason::Reason::
                kPrinterUnreachable,
            CupsPrinterStatus::CupsPrinterStatusReason::Severity::kError);
      }
      SendPrinterStatus(printer_status, std::move(cb),
                        /*notify_observers=*/true);
      return;
    }

    // Behavior for querying a non-IPP uri is undefined and disallowed.
    if (!IsIppUri(printer->uri())) {
      PRINTER_LOG(DEBUG) << "Unable to complete printer status request. "
                         << "Printer uri is invalid. Printer id: "
                         << printer_id;
      CupsPrinterStatus printer_status(printer_id);
      printer_status.AddStatusReason(
          CupsPrinterStatus::CupsPrinterStatusReason::Reason::kUnknownReason,
          CupsPrinterStatus::CupsPrinterStatusReason::Severity::kWarning);
      SendPrinterStatus(printer_status, std::move(cb),
                        /*notify_observers=*/false);
      return;
    }

    QueryIppPrinter(
        printer->uri().GetHostEncoded(), printer->uri().GetPort(),
        printer->uri().GetPathEncodedAsString(),
        printer->uri().GetScheme() == chromeos::kIppsScheme,
        base::BindOnce(&CupsPrintersManagerImpl::OnPrinterInfoFetched,
                       weak_ptr_factory_.GetWeakPtr(), printer_id,
                       std::move(cb)));
  }

  // Public API function.
  void RecordNearbyNetworkPrinterCounts() const override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);

    size_t total_network_printers_count = zeroconf_detections_.size();
    // Count detected network printers that have not been saved
    size_t nearby_zeroconf_printers_count = 0;
    for (const PrinterDetector::DetectedPrinter& detected :
         zeroconf_detections_) {
      if (!printers_.IsPrinterInClass(PrinterClass::kSaved,
                                      detected.printer.id())) {
        ++nearby_zeroconf_printers_count;
      }
    }

    base::UmaHistogramCounts100(
        "Printing.CUPS.TotalNetworkPrintersCount2.SettingsOpened",
        total_network_printers_count);
    base::UmaHistogramCounts100("Printing.CUPS.NearbyNetworkPrintersCount",
                                nearby_zeroconf_printers_count);
  }

  PrintServersManager* GetPrintServersManager() const override {
    return print_servers_manager_.get();
  }

  // Callback for FetchPrinterStatus
  void OnPrinterInfoFetched(
      const std::string& printer_id,
      PrinterStatusCallback cb,
      PrinterQueryResult result,
      const ::printing::PrinterStatus& printer_status,
      const std::string& make_and_model,
      const std::vector<std::string>& document_formats,
      bool ipp_everywhere,
      const chromeos::PrinterAuthenticationInfo& auth_info) {
    ParsePrinterStatusFromPrinterQuery(printer_id, std::move(cb), result,
                                       printer_status, auth_info);
  }

  void ParsePrinterStatusFromPrinterQuery(
      const std::string& printer_id,
      PrinterStatusCallback cb,
      PrinterQueryResult result,
      const ::printing::PrinterStatus& printer_status,
      const chromeos::PrinterAuthenticationInfo& auth_info) {
    base::UmaHistogramEnumeration("Printing.CUPS.PrinterStatusQueryResult",
                                  result);
    switch (result) {
      case PrinterQueryResult::kHostnameResolution:
      case PrinterQueryResult::kUnreachable: {
        PRINTER_LOG(ERROR)
            << "Printer status request failed. Could not reach printer "
            << printer_id;
        CupsPrinterStatus error_printer_status(printer_id);
        error_printer_status.AddStatusReason(
            CupsPrinterStatus::CupsPrinterStatusReason::Reason::
                kPrinterUnreachable,
            CupsPrinterStatus::CupsPrinterStatusReason::Severity::kError);
        SendPrinterStatus(error_printer_status, std::move(cb),
                          /*notify_observers=*/true);
        break;
      }
      case PrinterQueryResult::kUnknownFailure: {
        PRINTER_LOG(ERROR) << "Printer status request failed. Unknown failure "
                              "trying to reach printer "
                           << printer_id;
        CupsPrinterStatus error_printer_status(printer_id);
        error_printer_status.AddStatusReason(
            CupsPrinterStatus::CupsPrinterStatusReason::Reason::kUnknownReason,
            CupsPrinterStatus::CupsPrinterStatusReason::Severity::kWarning);
        SendPrinterStatus(error_printer_status, std::move(cb),
                          /*notify_observers=*/true);
        break;
      }
      case PrinterQueryResult::kSuccess: {
        // Record results from PrinterStatus before converting to
        // CupsPrinterStatus because the PrinterStatus enum contains more reason
        // buckets.
        for (const auto& reason : printer_status.reasons) {
          base::UmaHistogramEnumeration("Printing.CUPS.PrinterStatusReasons",
                                        reason.reason);
        }

        // Convert printing::PrinterStatus to printing::CupsPrinterStatus
        CupsPrinterStatus cups_printers_status =
            PrinterStatusToCupsPrinterStatus(printer_id, printer_status,
                                             auth_info);

        // Send status back to the handler through PrinterStatusCallback.
        SendPrinterStatus(cups_printers_status, std::move(cb),
                          /*notify_observers=*/true);
        break;
      }
    }
  }

  // Sends the printer status via callback then notifies the local printer
  // observers.
  void SendPrinterStatus(CupsPrinterStatus printer_status,
                         PrinterStatusCallback cb,
                         bool notify_observers) {
    if (notify_observers) {
      // Save the status so it can be attached with the printer for future
      // retrievals.
      const bool is_new_status = printers_.SavePrinterStatus(
          printer_status.GetPrinterId(), printer_status);
      if (is_new_status) {
        NotifyLocalPrinterObservers();
      }
    }
    std::move(cb).Run(std::move(printer_status));
  }

  void QueryPrinterForAutoConf(
      const Printer& printer,
      base::OnceCallback<void(bool)> callback) override {
    if (!IsIppUri(printer.uri())) {
      std::move(callback).Run(false);
      return;
    }

    QueryIppPrinter(
        printer.uri().GetHostEncoded(), printer.uri().GetPort(),
        printer.uri().GetPathEncodedAsString(),
        printer.uri().GetScheme() == chromeos::kIppsScheme,
        base::BindOnce(&CupsPrintersManagerImpl::OnQueryPrinterForAutoConf,
                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
  }

  // Callback for QueryPrinterForAutoConf
  void OnQueryPrinterForAutoConf(
      base::OnceCallback<void(bool)> callback,
      PrinterQueryResult result,
      const ::printing::PrinterStatus& printer_status,
      const std::string& make_and_model,
      const std::vector<std::string>& document_formats,
      bool ipp_everywhere,
      const chromeos::PrinterAuthenticationInfo& auth_info) {
    if (result != PrinterQueryResult::kSuccess) {
      std::move(callback).Run(false);
      return;
    }

    std::move(callback).Run(ipp_everywhere);
  }

 private:
  std::optional<Printer> GetEnterprisePrinter(const std::string& id) const {
    return printers_.Get(PrinterClass::kEnterprise, id);
  }

  // TODO(baileyberro): Remove the need for this function by pushing additional
  // logic into PrintersMap. https://crbug.com/956172
  void ResetNearbyPrintersLists() {
    printers_.Clear(PrinterClass::kAutomatic);
    printers_.Clear(PrinterClass::kDiscovered);
  }

  // Notify observers on the given classes the the relevant lists have changed.
  void NotifyObservers(const std::vector<PrinterClass>& printer_classes) {
    for (auto printer_class : printer_classes) {
      auto printers = printers_.Get(printer_class);
      PRINTER_LOG(DEBUG) << "Sending notification for " << printers.size()
                         << " printers in class (" << ToString(printer_class)
                         << ")";
      for (auto& observer : observer_list_) {
        observer.OnPrintersChanged(printer_class, printers);
      }
    }
  }

  // Notify observers that a local printer has updated.
  void NotifyLocalPrinterObservers() {
    for (auto& observer : local_printers_observer_list_) {
      observer.OnLocalPrintersUpdated();
    }
  }

  // Look through all sources for the detected printer with the given id.
  // Return a pointer to the printer on found, null if no entry is found.
  const PrinterDetector::DetectedPrinter* FindDetectedPrinter(
      const std::string& id) const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    for (const auto* printer_list : {&usb_detections_, &zeroconf_detections_}) {
      for (const auto& detected : *printer_list) {
        if (detected.printer.id() == id) {
          return &detected;
        }
      }
    }
    return nullptr;
  }

  void MaybeRecordInstallation(const Printer& printer,
                               bool is_automatic_installation) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (synced_printers_manager_->GetPrinter(printer.id())) {
      // It's just an update, not a new installation, so don't record an event.
      return;
    }

    // For compatibility with the previous implementation, record USB printers
    // separately from other IPP printers.  Eventually we may want to shift
    // this to be split by autodetected/not autodetected instead of USB/other
    // IPP.
    if (printer.IsUsbProtocol()) {
      // Get the associated detection record if one exists.
      const auto* detected = FindDetectedPrinter(printer.id());
      // We should have the full DetectedPrinter.  We can't log the printer if
      // we don't have it.
      if (!detected) {
        LOG(WARNING) << "Failed to find USB printer " << printer.id()
                     << " for installation event logging";
        return;
      }
      // For recording purposes, this is an automatic install if the ppd
      // reference generated at detection time is the is the one we actually
      // used -- i.e. the user didn't have to change anything to obtain a ppd
      // that worked.
      PrinterEventTracker::SetupMode mode;
      if (is_automatic_installation) {
        mode = PrinterEventTracker::kAutomatic;
      } else {
        mode = PrinterEventTracker::kUser;
      }
      event_tracker_->RecordUsbPrinterInstalled(*detected, mode);
    } else {
      PrinterEventTracker::SetupMode mode;
      if (is_automatic_installation) {
        mode = PrinterEventTracker::kAutomatic;
      } else {
        mode = PrinterEventTracker::kUser;
      }
      event_tracker_->RecordIppPrinterInstalled(printer, mode);
    }
  }

  void AddDetectedUsbPrinters(
      const std::vector<PrinterDetector::DetectedPrinter>& detected_list) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);

    // Update the list of connected printers (skip the saved ones).
    std::vector<PrinterDetector::DetectedPrinter> printers;
    for (const PrinterDetector::DetectedPrinter& detected : detected_list) {
      if (!printers_.IsPrinterInClass(PrinterClass::kSaved,
                                      detected.printer.id())) {
        printers.push_back(detected);
      }
    }
    auto_usb_printer_configurer_->UpdateListOfConnectedPrinters(
        std::move(printers));

    // Update lists of Automatic and Discovered printers.
    for (const std::string& printer_id :
         auto_usb_printer_configurer_->ConfiguredPrintersIds()) {
      if (!printers_.IsPrinterInClass(PrinterClass::kAutomatic, printer_id)) {
        AddPrinterToPrintersMap(
            PrinterClass::kAutomatic,
            auto_usb_printer_configurer_->Printer(printer_id));
      }
    }
    for (const std::string& printer_id :
         auto_usb_printer_configurer_->UnconfiguredPrintersIds()) {
      if (!printers_.IsPrinterInClass(PrinterClass::kDiscovered, printer_id)) {
        AddPrinterToPrintersMap(
            PrinterClass::kDiscovered,
            auto_usb_printer_configurer_->Printer(printer_id));
      }
    }
  }

  void AddDetectedNetworkPrinters(
      const std::vector<PrinterDetector::DetectedPrinter>& detected_list) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    for (const PrinterDetector::DetectedPrinter& detected : detected_list) {
      const std::string& detected_printer_id = detected.printer.id();
      if (printers_.IsPrinterInClass(PrinterClass::kSaved,
                                     detected_printer_id)) {
        // It's already in the saved class, don't need to do anything else here.
        continue;
      }

      // Sometimes the detector can flag a printer as IPP-everywhere compatible;
      // those printers can go directly into the automatic class without further
      // processing.
      auto printer = detected.printer;
      if (printer.IsIppEverywhere()) {
        AddPrinterToPrintersMap(PrinterClass::kAutomatic, printer);
        continue;
      }

      if (!ppd_resolution_tracker_.IsResolutionComplete(detected_printer_id)) {
        // Didn't find an entry for this printer in the PpdReferences cache.  We
        // need to ask PpdProvider whether or not it can determine a
        // PpdReference.  If there's not already an outstanding request for one,
        // start one.  When the request comes back, we'll rerun classification
        // and then should be able to figure out where this printer belongs.
        if (!ppd_resolution_tracker_.IsResolutionPending(detected_printer_id)) {
          ppd_resolution_tracker_.MarkResolutionPending(detected_printer_id);
          ppd_provider_->ResolvePpdReference(
              detected.ppd_search_data,
              base::BindOnce(&CupsPrintersManagerImpl::ResolvePpdReferenceDone,
                             weak_ptr_factory_.GetWeakPtr(),
                             detected_printer_id));
        }
        continue;
      }
      if (ppd_resolution_tracker_.WasResolutionSuccessful(
              detected_printer_id)) {
        // We have a ppd reference, so we think we can set this up
        // automatically.
        *printer.mutable_ppd_reference() =
            ppd_resolution_tracker_.GetPpdReference(detected_printer_id);
        AddPrinterToPrintersMap(PrinterClass::kAutomatic, printer);
        continue;
      }

      // We are not able to set the printer up automatically.
      AddPrinterToPrintersMap(PrinterClass::kDiscovered, printer);
    }
  }

  void AddPrinterToPrintersMap(PrinterClass printer_class,
                               const Printer& printer) {
    printers_.Insert(printer_class, printer);

    // If we've seen this printer before, don't trigger a new detection event.
    if (detected_printers_seen_.contains(printer.id())) {
      return;
    }

    detected_printers_seen_.insert(printer.id());
    NotifyLocalPrinterObservers();
  }

  // Returns true if we've disconnected from our current network. Updates
  // the current active network. This method is not reentrant.
  bool HasNetworkDisconnected(
      const std::vector<
          chromeos::network_config::mojom::NetworkStatePropertiesPtr>&
          networks) {
    // An empty current_network indicates that we're not connected to a valid
    // network right now.
    std::string current_network;
    if (!networks.empty()) {
      // The first network is the default network which receives mDNS
      // multicasts.
      current_network = networks.front()->guid;
    }

    // If we attach to a network after being disconnected, we do not want to
    // forcibly clear our detected list.  It is either already empty or contains
    // valid entries because we missed the original connection event.
    bool network_disconnected =
        !active_network_.empty() && current_network != active_network_;

    // Ensure that we don't register network state updates as network changes.
    active_network_ = std::move(current_network);

    return network_disconnected;
  }

  // Record in UMA the appropriate event with a setup attempt for a printer is
  // abandoned.
  void RecordSetupAbandoned(const Printer& printer) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (printer.IsUsbProtocol()) {
      const auto* detected = FindDetectedPrinter(printer.id());
      if (!detected) {
        LOG(WARNING) << "Failed to find USB printer " << printer.id()
                     << " for abandoned event logging";
        return;
      }
      event_tracker_->RecordUsbSetupAbandoned(*detected);
    } else {
      event_tracker_->RecordSetupAbandoned(printer);
    }
  }

  // Rebuild the Automatic and Discovered printers lists from the (cached) raw
  // detections.  This will also generate OnPrintersChanged events for any
  // observers observering either of the detected lists (kAutomatic and
  // kDiscovered).
  void RebuildDetectedLists() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    ResetNearbyPrintersLists();
    AddDetectedUsbPrinters(usb_detections_);
    AddDetectedNetworkPrinters(zeroconf_detections_);
    AddDetectedNetworkPrinters(servers_detections_);
    NotifyObservers({PrinterClass::kAutomatic, PrinterClass::kDiscovered});
  }

  void OnUsbPrinterSetupDone(std::string printer_id) {
    if (auto_usb_printer_configurer_->ConfiguredPrintersIds().contains(
            printer_id)) {
      AddPrinterToPrintersMap(
          PrinterClass::kAutomatic,
          auto_usb_printer_configurer_->Printer(printer_id));
      NotifyObservers({PrinterClass::kAutomatic});
    } else {
      AddPrinterToPrintersMap(
          PrinterClass::kDiscovered,
          auto_usb_printer_configurer_->Printer(printer_id));
      NotifyObservers({PrinterClass::kDiscovered});
    }
  }

  // Callback invoked on completion of PpdProvider::ResolvePpdReference.
  void ResolvePpdReferenceDone(const std::string& printer_id,
                               PpdProvider::CallbackResultCode code,
                               const Printer::PpdReference& ref,
                               const std::string& usb_manufacturer) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (code == PpdProvider::SUCCESS) {
      ppd_resolution_tracker_.MarkResolutionSuccessful(printer_id, ref);
    } else {
      LOG(WARNING) << "Failed to resolve PPD reference for " << printer_id;
      ppd_resolution_tracker_.MarkResolutionFailed(printer_id);
      if (!usb_manufacturer.empty()) {
        ppd_resolution_tracker_.SetManufacturer(printer_id, usb_manufacturer);
      }
    }
    RebuildDetectedLists();
  }

  // Callback for `SetUpPrinterInCups`.
  void OnPrinterSetupResult(const std::string& printer_id,
                            bool is_automatic_installation,
                            PrinterSetupResult result) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);

    std::map<std::string, PrinterSetupTracker>::iterator it =
        printers_being_setup_.find(printer_id);
    DCHECK(it != printers_being_setup_.end());

    if (result == PrinterSetupResult::kSuccess) {
      installed_printer_fingerprints_[printer_id] = it->second.fingerprint;
      // TODO: b/295243026 - Solve this issue during metrics clean-up.
      // We check this condition before calling MaybeRecordInstallation() to
      // make it backward compatible with the state before crrev.com/c/4763464.
      // MaybeRecordInstallation() is used only for reporting and changing the
      // condition below may have significant influence on some metrics.
      // The better solution would be, instead of checking this flag, to NOT
      // record events for server and enterprise printers.
      if (user_printers_allowed_.GetValue()) {
        std::optional<chromeos::Printer> printer = printers_.Get(printer_id);
        if (printer) {
          MaybeRecordInstallation(*printer, is_automatic_installation);
        }
      }
    }

    std::vector<PrinterSetupCallback> callbacks =
        std::move(it->second.callbacks);
    printers_being_setup_.erase(it);

    for (auto& callback : callbacks) {
      std::move(callback).Run(result);
    }
  }

  // Resets all network detected printer lists.
  void ClearNetworkDetectedPrinters() {
    PRINTER_LOG(DEBUG) << "Clear network printers";
    zeroconf_detections_.clear();

    ResetNearbyPrintersLists();
  }

  void RecordTotalNearbyNetworkPrinterCounts() {
    base::UmaHistogramCounts100("Printing.CUPS.TotalNetworkPrintersCount2",
                                zeroconf_detections_.size());
  }

  SEQUENCE_CHECKER(sequence_);

  // Source lists for detected printers.
  std::vector<PrinterDetector::DetectedPrinter> usb_detections_;
  std::vector<PrinterDetector::DetectedPrinter> zeroconf_detections_;
  std::vector<PrinterDetector::DetectedPrinter> servers_detections_;

  // Not owned.
  const raw_ptr<SyncedPrintersManager> synced_printers_manager_;
  base::ScopedObservation<SyncedPrintersManager,
                          SyncedPrintersManager::Observer>
      synced_printers_manager_observation_{this};
  mojo::Remote<chromeos::network_config::mojom::CrosNetworkConfig>
      remote_cros_network_config_;
  mojo::Receiver<chromeos::network_config::mojom::CrosNetworkConfigObserver>
      cros_network_config_observer_receiver_{this};

  std::unique_ptr<PrinterDetector> usb_detector_;

  std::unique_ptr<PrinterDetector> zeroconf_detector_;

  scoped_refptr<PpdProvider> ppd_provider_;
  raw_ptr<DlcserviceClient> dlc_service_client_;

  std::unique_ptr<UsbPrinterNotificationController>
      usb_notification_controller_;

  std::unique_ptr<AutomaticUsbPrinterConfigurer> auto_usb_printer_configurer_;

  std::unique_ptr<PrintServersManager> print_servers_manager_;

  std::unique_ptr<EnterprisePrintersProvider> enterprise_printers_provider_;
  base::ScopedObservation<EnterprisePrintersProvider,
                          EnterprisePrintersProvider::Observer>
      enterprise_printers_provider_observation_{this};

  // Not owned
  const raw_ptr<PrinterEventTracker> event_tracker_;

  // Categorized printers.  This is indexed by PrinterClass.
  PrintersMap printers_;

  // Equals true if the list of enterprise printers and related policies
  // is initialized and configured correctly.
  bool enterprise_printers_are_ready_ = false;

  // GUID of the current default network.
  std::string active_network_;

  // Tracks PpdReference resolution. Also stores USB manufacturer string if
  // available.
  PpdResolutionTracker ppd_resolution_tracker_;

  // Map of printer ids to PrinterConfigurer setup fingerprints at the time
  // the printers was last installed with CUPS.
  std::map<std::string, std::string> installed_printer_fingerprints_;

  // List of printers being currently setup in CUPS.
  struct PrinterSetupTracker {
    std::unique_ptr<PrinterConfigurer> configurer;
    std::string fingerprint;
    std::vector<PrinterSetupCallback> callbacks;
  };
  std::map<std::string, PrinterSetupTracker> printers_being_setup_;

  base::ObserverList<CupsPrintersManager::Observer>::Unchecked observer_list_;

  // Maintains list of observers for updates to local printers.
  base::ObserverList<CupsPrintersManager::LocalPrintersObserver>
      local_printers_observer_list_;

  // Holds the current value of the pref |UserPrintersAllowed|.
  BooleanPrefMember user_printers_allowed_;

  // Timer used to prevent the total nearby printers from immediately recording
  // each time the mDNS reports printers.
  base::DelayTimer nearby_printers_metric_delay_timer_;

  // Tracks the printers seen from mDNS or USB plug ins so the
  // LocalPrinterObserver knows when to fire for a new printer.
  // TODO(b/304269962): Remove detected printers from here when disconnected
  // from the device.
  base::flat_set<std::string> detected_printers_seen_;

  // Once elapsed, executes a round of printer status queries.
  base::OneShotTimer printer_status_good_state_timer_;
  base::OneShotTimer printer_status_unreachable_state_timer_;

  // Used to limit the total duration of printer status polling.
  std::unique_ptr<base::ElapsedTimer>
      printer_status_polling_total_duration_timer_;

  base::WeakPtrFactory<CupsPrintersManagerImpl> weak_ptr_factory_{this};
};

}  // namespace

// static
std::unique_ptr<CupsPrintersManager> CupsPrintersManager::Create(
    Profile* profile) {
  return std::make_unique<CupsPrintersManagerImpl>(
      SyncedPrintersManagerFactory::GetInstance()->GetForBrowserContext(
          profile),
      UsbPrinterDetector::Create(), ZeroconfPrinterDetector::Create(),
      CreatePpdProvider(profile), DlcserviceClient::Get(),
      UsbPrinterNotificationController::Create(profile),
      PrintServersManager::Create(profile),
      EnterprisePrintersProvider::Create(CrosSettings::Get(), profile),
      PrinterEventTrackerFactory::GetInstance()->GetForBrowserContext(profile),
      profile->GetPrefs());
}

// static
std::unique_ptr<CupsPrintersManager> CupsPrintersManager::CreateForTesting(
    SyncedPrintersManager* synced_printers_manager,
    std::unique_ptr<PrinterDetector> usb_detector,
    std::unique_ptr<PrinterDetector> zeroconf_detector,
    scoped_refptr<PpdProvider> ppd_provider,
    DlcserviceClient* dlc_service_client,
    std::unique_ptr<UsbPrinterNotificationController>
        usb_notification_controller,
    std::unique_ptr<PrintServersManager> print_servers_manager,
    std::unique_ptr<EnterprisePrintersProvider> enterprise_printers_provider,
    PrinterEventTracker* event_tracker,
    PrefService* pref_service) {
  return std::make_unique<CupsPrintersManagerImpl>(
      synced_printers_manager, std::move(usb_detector),
      std::move(zeroconf_detector), std::move(ppd_provider), dlc_service_client,
      std::move(usb_notification_controller), std::move(print_servers_manager),
      std::move(enterprise_printers_provider), event_tracker, pref_service);
}

// static
void CupsPrintersManager::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(
      prefs::kUserPrintersAllowed, true,
      user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF);
  registry->RegisterBooleanPref(prefs::kPrintingSendUsernameAndFilenameEnabled,
                                false);
  PrintServersProvider::RegisterProfilePrefs(registry);
}

// static
void CupsPrintersManager::RegisterLocalStatePrefs(
    PrefRegistrySimple* registry) {
  PrintServersProvider::RegisterLocalStatePrefs(registry);
  printing::oauth2::ClientIdsDatabase::RegisterLocalStatePrefs(registry);
}

}  // namespace ash