blob: 53e3828dc02baf468e6d11325cef02572b8e3d5b [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/printing/cups_printers_manager.h"
#include <algorithm>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/memory/singleton.h"
#include "base/observer_list.h"
#include "base/scoped_observer.h"
#include "base/sequence_checker.h"
#include "chrome/browser/chromeos/printing/ppd_provider_factory.h"
#include "chrome/browser/chromeos/printing/printer_event_tracker_factory.h"
#include "chrome/browser/chromeos/printing/synced_printers_manager.h"
#include "chrome/browser/chromeos/printing/synced_printers_manager_factory.h"
#include "chrome/browser/chromeos/printing/usb_printer_detector.h"
#include "chrome/browser/chromeos/printing/zeroconf_printer_detector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.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"
namespace chromeos {
namespace {
class CupsPrintersManagerImpl;
// Since CupsPrintersManager listens to multiple PrinterDetectors, we need to
// disambiguate incoming observer calls based on their source, and so can't
// implement PrinterDetector::Observer directly in CupsPrintersManagerImpl.
//
// Note that at the time the Proxy is constructed, CupsPrintersManagerImpl's
// construction may not be complete, so any callbacks into the parent need
// to be deferred.
class PrinterDetectorObserverProxy : public PrinterDetector::Observer {
public:
PrinterDetectorObserverProxy(CupsPrintersManagerImpl* parent,
int id,
PrinterDetector* detector)
: parent_(parent), id_(id), observer_(this) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
// It's ok to Add() before construction is complete because callbacks are on
// the same sequence, therefore we will complete construction before any
// detection callback will be processed.
observer_.Add(detector);
}
~PrinterDetectorObserverProxy() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
}
// Defined out of line because we need the CupsPrintersManagerImpl
// definition first.
void OnPrintersFound(
const std::vector<PrinterDetector::DetectedPrinter>& printers) override;
private:
CupsPrintersManagerImpl* parent_;
int id_;
SEQUENCE_CHECKER(sequence_);
ScopedObserver<PrinterDetector, PrinterDetector::Observer> observer_;
};
// This is akin to python's filter() builtin, but with reverse polarity on the
// test function -- *remove* all entries in printers for which test_fn returns
// true, discard the rest.
void FilterOutPrinters(std::vector<Printer>* printers,
std::function<bool(const Printer&)> test_fn) {
auto new_end = std::remove_if(printers->begin(), printers->end(), test_fn);
printers->resize(new_end - printers->begin());
}
// Return true if this is a USB printer.
bool IsUsbPrinter(const Printer& printer) {
return base::StringPiece(printer.uri()).starts_with("usb://");
}
class CupsPrintersManagerImpl : public CupsPrintersManager,
public SyncedPrintersManager::Observer {
public:
// Identifiers for each of the underlying PrinterDetectors this
// class observes.
enum DetectorIds {
kUsbDetector,
kZeroconfDetector,
};
CupsPrintersManagerImpl(SyncedPrintersManager* synced_printers_manager,
std::unique_ptr<PrinterDetector> usb_detector,
std::unique_ptr<PrinterDetector> zeroconf_detector,
scoped_refptr<PpdProvider> ppd_provider,
PrinterEventTracker* event_tracker,
PrefService* pref_service)
: synced_printers_manager_(synced_printers_manager),
synced_printers_manager_observer_(this),
usb_detector_(std::move(usb_detector)),
zeroconf_detector_(std::move(zeroconf_detector)),
ppd_provider_(std::move(ppd_provider)),
event_tracker_(event_tracker),
printers_(kNumPrinterClasses),
weak_ptr_factory_(this) {
// Prime the printer cache with the configured and enterprise printers.
printers_[kConfigured] = synced_printers_manager_->GetConfiguredPrinters();
RebuildConfiguredPrintersIndex();
printers_[kEnterprise] = synced_printers_manager_->GetEnterprisePrinters();
synced_printers_manager_observer_.Add(synced_printers_manager_);
// Callbacks may ensue immediately when the observer proxies are set up, so
// these instantiations must come after everything else is initialized.
usb_detector_observer_proxy_ =
std::make_unique<PrinterDetectorObserverProxy>(this, kUsbDetector,
usb_detector_.get());
OnPrintersFound(kUsbDetector, usb_detector_->GetPrinters());
zeroconf_detector_observer_proxy_ =
std::make_unique<PrinterDetectorObserverProxy>(
this, kZeroconfDetector, zeroconf_detector_.get());
OnPrintersFound(kZeroconfDetector, zeroconf_detector_->GetPrinters());
native_printers_allowed_.Init(prefs::kUserNativePrintersAllowed,
pref_service);
}
~CupsPrintersManagerImpl() override = default;
// Public API function.
std::vector<Printer> GetPrinters(PrinterClass printer_class) const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (!native_printers_allowed_.GetValue() && printer_class != kEnterprise) {
// If native printers are disabled then simply return an empty vector.
LOG(WARNING) << "Attempting to retrieve native printers when "
"UserNativePrintersAllowed is set to false";
return {};
}
return printers_.at(printer_class);
}
// Public API function.
void RemoveUnavailablePrinters(
std::vector<Printer>* printers) const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
FilterOutPrinters(printers, [this](const Printer& printer) {
return !PrinterAvailable(printer);
});
}
// Public API function.
void UpdateConfiguredPrinter(const Printer& printer) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (!native_printers_allowed_.GetValue()) {
LOG(WARNING) << "UpdateConfiguredPrinter() called when "
"UserNativePrintersAllowed is set to false";
return;
}
// If this is an 'add' instead of just an update, record the event.
MaybeRecordInstallation(printer);
synced_printers_manager_->UpdateConfiguredPrinter(printer);
// Note that we will rebuild our lists when we get the observer
// callback from |synced_printers_manager_|.
}
// Public API function.
void RemoveConfiguredPrinter(const std::string& printer_id) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
auto existing = synced_printers_manager_->GetPrinter(printer_id);
if (existing != nullptr) {
event_tracker_->RecordPrinterRemoved(*existing);
}
synced_printers_manager_->RemoveConfiguredPrinter(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);
}
// Public API function.
void RemoveObserver(CupsPrintersManager::Observer* observer) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
observer_list_.RemoveObserver(observer);
}
// Public API function.
void PrinterInstalled(const Printer& printer) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (!native_printers_allowed_.GetValue()) {
LOG(WARNING) << "PrinterInstalled() called when "
"UserNativePrintersAllowed is set to false";
return;
}
MaybeRecordInstallation(printer);
synced_printers_manager_->PrinterInstalled(printer);
}
// Public API function.
bool IsPrinterInstalled(const Printer& printer) const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
return synced_printers_manager_->IsConfigurationCurrent(printer);
}
// Public API function.
// Note this is linear in the number of printers. If the number of printers
// gets so large that a linear search is prohibative, we'll have to rethink
// more than just this function.
std::unique_ptr<Printer> GetPrinter(const std::string& id) const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (!native_printers_allowed_.GetValue()) {
LOG(WARNING) << "UserNativePrintersAllowed is disabled - only searching "
"enterprise printers";
return GetEnterprisePrinter(id);
}
for (const auto& printer_list : printers_) {
for (const auto& printer : printer_list) {
if (printer.id() == id) {
return std::make_unique<Printer>(printer);
}
}
}
return std::unique_ptr<Printer>();
}
// SyncedPrintersManager::Observer implementation
void OnConfiguredPrintersChanged(
const std::vector<Printer>& printers) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
printers_[kConfigured] = printers;
RebuildConfiguredPrintersIndex();
RebuildDetectedLists();
UpdateConfiguredPrinterURIs();
NotifyObservers({kConfigured});
}
// SyncedPrintersManager::Observer implementation
void OnEnterprisePrintersChanged(
const std::vector<Printer>& printers) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
printers_[kEnterprise] = printers;
NotifyObservers({kEnterprise});
}
// Callback entry point for PrinterDetectorObserverProxys owned by this
// object.
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;
break;
}
RebuildDetectedLists();
// We may have new URI information for a configured printer in the changed
// detected list. If we do, pass the updated information along to
// observers.
if (UpdateConfiguredPrinterURIs()) {
NotifyObservers({kConfigured});
}
}
private:
std::unique_ptr<Printer> GetEnterprisePrinter(const std::string& id) const {
for (const auto& printer : printers_[kEnterprise]) {
if (printer.id() == id) {
return std::make_unique<Printer>(printer);
}
}
return nullptr;
}
// Notify observers on the given classes the the relevant lists have changed.
void NotifyObservers(
const std::vector<CupsPrintersManager::PrinterClass>& printer_classes) {
for (auto& observer : observer_list_) {
for (auto printer_class : printer_classes) {
observer.OnPrintersChanged(printer_class, printers_[printer_class]);
}
}
}
// Rebuild the index from printer id to index for configured printers.
void RebuildConfiguredPrintersIndex() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
configured_printers_index_.clear();
for (size_t i = 0; i < printers_[kConfigured].size(); ++i) {
configured_printers_index_[printers_[kConfigured][i].id()] = i;
}
}
// Cross reference the Configured printers with the raw detected printer
// lists. Returns true if any entries in the configured printers list
// changed as a result of this cross referencing, false otherwise.
bool UpdateConfiguredPrinterURIs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
bool updated = false;
for (const auto* printer_list : {&usb_detections_, &zeroconf_detections_}) {
for (const auto& detected : *printer_list) {
auto configured =
configured_printers_index_.find(detected.printer.id());
if (configured != configured_printers_index_.end()) {
Printer* configured_printer =
&printers_[kConfigured][configured->second];
if (configured_printer->effective_uri() !=
detected.printer.effective_uri()) {
configured_printer->set_effective_uri(
detected.printer.effective_uri());
updated = true;
}
}
}
}
return updated;
}
// 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) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
if (synced_printers_manager_->GetPrinter(printer.id()) != nullptr) {
// It's just an update, not a new installation, so don't record an event.
return;
}
// Get the associated detection record if one exists.
const auto* detected = FindDetectedPrinter(printer.id());
// 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 (IsUsbPrinter(printer)) {
// If it's a usb printer, we should have the full DetectedPrinter. We
// can't log the printer if we don't have it.
if (detected == nullptr) {
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 (printer.ppd_reference() == detected->printer.ppd_reference()) {
mode = PrinterEventTracker::kAutomatic;
} else {
mode = PrinterEventTracker::kUser;
}
event_tracker_->RecordUsbPrinterInstalled(*detected, mode);
} else {
PrinterEventTracker::SetupMode mode;
if (detected != nullptr &&
(detected->printer.ppd_reference().autoconf ||
(detected->printer.ppd_reference() == printer.ppd_reference()))) {
// A printer is automatic if we successfully used IPP Anywhere or we
// auto-grabbed a ppd reference that worked.
mode = PrinterEventTracker::kAutomatic;
} else {
mode = PrinterEventTracker::kUser;
}
event_tracker_->RecordIppPrinterInstalled(printer, mode);
}
}
// Return whether or not we believe this printer is currently available for
// printing. This is not a perfect test -- we just assume any IPP printers
// are available because, in cases where there are a large number of
// printers available, probing IPP printers would generate too much network
// spam. This is intended to help filter out local printers that are not
// available (USB, zeroconf, ...)
//
// TODO(justincarlson) - Implement this. Until it's implemented, we'll never
// filter out unavailable printers from potential printer targets. While
// suboptimal, this is ok--it just means that we will fail to print if the
// user selects a printer that's not available.
bool PrinterAvailable(const Printer& printer) const { return true; }
void AddDetectedList(
const std::vector<PrinterDetector::DetectedPrinter>& detected_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
for (const PrinterDetector::DetectedPrinter& detected : detected_list) {
if (base::ContainsKey(configured_printers_index_,
detected.printer.id())) {
// It's already in the configured 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.
if (detected.printer.IsIppEverywhere()) {
printers_[kAutomatic].push_back(detected.printer);
continue;
}
auto it = detected_printer_ppd_references_.find(detected.printer.id());
if (it != detected_printer_ppd_references_.end()) {
if (it->second == nullptr) {
// If the detected printer supports ipp-over-usb and we could not find
// a ppd for it, then we switch to the ippusb scheme and mark it as
// autoconf.
auto printer = detected.printer;
if (printer.supports_ippusb()) {
printer.set_uri(
base::StringPrintf("ippusb://%04x_%04x/ipp/print",
detected.ppd_search_data.usb_vendor_id,
detected.ppd_search_data.usb_product_id));
printer.mutable_ppd_reference()->autoconf = true;
}
// We couldn't figure out this printer, so it's in the discovered
// class.
printers_[kDiscovered].push_back(printer);
} else {
// We have a ppd reference, so we think we can set this up
// automatically.
printers_[kAutomatic].push_back(detected.printer);
*printers_[kAutomatic].back().mutable_ppd_reference() = *it->second;
}
} else {
// 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 (!base::ContainsKey(inflight_ppd_reference_resolutions_,
detected.printer.id())) {
inflight_ppd_reference_resolutions_.insert(detected.printer.id());
ppd_provider_->ResolvePpdReference(
detected.ppd_search_data,
base::Bind(&CupsPrintersManagerImpl::ResolvePpdReferenceDone,
weak_ptr_factory_.GetWeakPtr(),
detected.printer.id()));
}
}
}
}
// 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 (IsUsbPrinter(printer)) {
const auto* detected = FindDetectedPrinter(printer.id());
if (detected == nullptr) {
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_);
printers_[kAutomatic].clear();
printers_[kDiscovered].clear();
AddDetectedList(usb_detections_);
AddDetectedList(zeroconf_detections_);
NotifyObservers({kAutomatic, kDiscovered});
}
// Callback invoked on completion of PpdProvider::ResolvePpdReference.
void ResolvePpdReferenceDone(const std::string& printer_id,
PpdProvider::CallbackResultCode code,
const Printer::PpdReference& ref) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
inflight_ppd_reference_resolutions_.erase(printer_id);
// Create the entry.
std::unique_ptr<Printer::PpdReference>& value =
detected_printer_ppd_references_[printer_id];
if (code == PpdProvider::SUCCESS) {
// If we got something, populate the entry. Otherwise let it
// just remain null.
value.reset(new Printer::PpdReference(ref));
}
RebuildDetectedLists();
}
SEQUENCE_CHECKER(sequence_);
// Source lists for detected printers.
std::vector<PrinterDetector::DetectedPrinter> usb_detections_;
std::vector<PrinterDetector::DetectedPrinter> zeroconf_detections_;
// Not owned.
SyncedPrintersManager* synced_printers_manager_;
ScopedObserver<SyncedPrintersManager, SyncedPrintersManager::Observer>
synced_printers_manager_observer_;
std::unique_ptr<PrinterDetector> usb_detector_;
std::unique_ptr<PrinterDetectorObserverProxy> usb_detector_observer_proxy_;
std::unique_ptr<PrinterDetector> zeroconf_detector_;
std::unique_ptr<PrinterDetectorObserverProxy>
zeroconf_detector_observer_proxy_;
scoped_refptr<PpdProvider> ppd_provider_;
// Not owned
PrinterEventTracker* event_tracker_;
// Categorized printers. This is indexed by PrinterClass.
std::vector<std::vector<Printer>> printers_;
// Printer ids that occur in one of our categories or printers.
std::unordered_set<std::string> known_printer_ids_;
// This is a dual-purpose structure. The keys in the map are printer ids.
// If an entry exists in this map it means we have received a response from
// PpdProvider about a PpdReference for the given printer. A null value
// means we don't have a PpdReference (and so can't set up this printer
// automatically).
std::unordered_map<std::string, std::unique_ptr<Printer::PpdReference>>
detected_printer_ppd_references_;
// Printer ids for which we have sent off a request to PpdProvider for a ppd
// reference, but have not yet gotten a response.
std::unordered_set<std::string> inflight_ppd_reference_resolutions_;
// Map from printer id to printers_[kConfigured] index for configured
// printers.
std::unordered_map<std::string, int> configured_printers_index_;
base::ObserverList<CupsPrintersManager::Observer> observer_list_;
// Holds the current value of the pref |UserNativePrintersAllowed|.
BooleanPrefMember native_printers_allowed_;
base::WeakPtrFactory<CupsPrintersManagerImpl> weak_ptr_factory_;
};
void PrinterDetectorObserverProxy::OnPrintersFound(
const std::vector<PrinterDetector::DetectedPrinter>& printers) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
parent_->OnPrintersFound(id_, printers);
}
} // 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),
PrinterEventTrackerFactory::GetInstance()->GetForBrowserContext(profile),
profile->GetPrefs());
}
// static
std::unique_ptr<CupsPrintersManager> CupsPrintersManager::Create(
SyncedPrintersManager* synced_printers_manager,
std::unique_ptr<PrinterDetector> usb_detector,
std::unique_ptr<PrinterDetector> zeroconf_detector,
scoped_refptr<PpdProvider> ppd_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), event_tracker,
pref_service);
}
// static
void CupsPrintersManager::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(
prefs::kUserNativePrintersAllowed, true,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}
} // namespace chromeos