chromium/chrome/browser/ash/printing/usb_printer_detector.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/usb_printer_detector.h"

#include <map>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "ash/public/cpp/session/session_controller.h"
#include "ash/public/cpp/session/session_observer.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/printing/ppd_provider_factory.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/synced_printers_manager_factory.h"
#include "chrome/browser/ash/printing/usb_printer_util.h"
#include "chrome/browser/browser_process.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/printing/ppd_provider.h"
#include "chromeos/printing/usb_printer_id.h"
#include "components/device_event_log/device_event_log.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/device_service.h"
#include "mojo/public/cpp/bindings/associated_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "services/device/public/mojom/usb_manager.mojom.h"
#include "services/device/public/mojom/usb_manager_client.mojom.h"

namespace ash {
namespace {

// Helper class, redirect calls to SessionController provided as a constructor's
// parameter. If the parameter is nullptr, the pointer returned by
// ash::SessionController::Get() is used (if not nullptr).
class SessionControllerWrapper {
 public:
  explicit SessionControllerWrapper(SessionController* session_controller)
      : session_controller_(session_controller) {}
  SessionControllerWrapper(const SessionControllerWrapper&) = delete;
  SessionControllerWrapper& operator=(const SessionControllerWrapper&) = delete;

  void AddObserver(SessionObserver* observer) const {
    if (SessionController* controller = GetSessionController(); controller) {
      controller->AddObserver(observer);
    }
  }
  void RemoveObserver(SessionObserver* observer) const {
    if (SessionController* controller = GetSessionController(); controller) {
      controller->RemoveObserver(observer);
    }
  }
  bool IsScreenLocked() const {
    if (SessionController* controller = GetSessionController(); controller) {
      return controller->IsScreenLocked();
    }
    return false;
  }

 private:
  SessionController* GetSessionController() const {
    if (session_controller_) {
      return session_controller_;
    }
    return SessionController::Get();
  }
  raw_ptr<SessionController> session_controller_;
};

// The PrinterDetector that drives the flow for setting up a USB printer to use
// CUPS backend.
class UsbPrinterDetectorImpl : public UsbPrinterDetector,
                               public device::mojom::UsbDeviceManagerClient,
                               public ash::SessionObserver {
 public:
  explicit UsbPrinterDetectorImpl(
      mojo::PendingRemote<device::mojom::UsbDeviceManager> device_manager,
      ash::SessionController* session_controller = nullptr)
      : device_manager_(std::move(device_manager)),
        session_controller_(session_controller) {
    device_manager_.set_disconnect_handler(
        base::BindOnce(&UsbPrinterDetectorImpl::OnDeviceManagerConnectionError,
                       weak_factory_.GetWeakPtr()));

    // Listen for added/removed device events.
    device_manager_->EnumerateDevicesAndSetClient(
        client_receiver_.BindNewEndpointAndPassRemote(),
        base::BindOnce(&UsbPrinterDetectorImpl::OnGetDevices,
                       weak_factory_.GetWeakPtr()));

    session_controller_.AddObserver(this);
  }

  ~UsbPrinterDetectorImpl() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    session_controller_.RemoveObserver(this);
  }

  // PrinterDetector override.
  void RegisterPrintersFoundCallback(OnPrintersFoundCallback cb) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    DCHECK(!on_printers_found_callback_);
    on_printers_found_callback_ = std::move(cb);
  }

  // PrinterDetector override.
  std::vector<DetectedPrinter> GetPrinters() override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    std::vector<DetectedPrinter> printers_list;
    printers_list.reserve(printers_ready_.size());
    for (const auto& entry : printers_ready_) {
      printers_list.push_back(entry.second);
    }
    return printers_list;
  }

 private:
  // Callback for initial enumeration of usb devices.
  void OnGetDevices(std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    for (const auto& device : devices) {
      DoAddDevice(*device);
    }
  }

  void OnDeviceManagerConnectionError() {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    device_manager_.reset();
    client_receiver_.reset();
    printers_ready_.clear();
    printers_locked_screen_.clear();
  }

  void DoAddDevice(const device::mojom::UsbDeviceInfo& device_info) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    if (!UsbDeviceIsPrinter(device_info)) {
      return;
    }

    DetectedPrinter entry;
    if (!UsbDeviceToPrinter(device_info, &entry)) {
      // An error will already have been logged if we failed to convert.
      PRINTER_LOG(EVENT) << "USB printer was detected but not recognized";
      return;
    }
    std::string make_and_model = GuessEffectiveMakeAndModel(device_info);
    PRINTER_LOG(EVENT) << "USB printer "
                       << base::StringPrintf("%04x:%04x", device_info.vendor_id,
                                             device_info.product_id)
                       << " was detected: " << make_and_model;

    entry.ppd_search_data.usb_vendor_id = device_info.vendor_id;
    entry.ppd_search_data.usb_product_id = device_info.product_id;
    entry.ppd_search_data.make_and_model.push_back(std::move(make_and_model));
    entry.ppd_search_data.discovery_type =
        chromeos::PrinterSearchData::PrinterDiscoveryType::kUsb;

    // Query printer for an IEEE Device ID.
    mojo::Remote<device::mojom::UsbDevice> device;
    device_manager_->GetDevice(device_info.guid,
                               /*blocked_interface_classes=*/{},
                               device.BindNewPipeAndPassReceiver(),
                               /*device_client=*/mojo::NullRemote());
    GetDeviceId(std::move(device),
                base::BindOnce(&UsbPrinterDetectorImpl::OnGetDeviceId,
                               weak_factory_.GetWeakPtr(), std::move(entry),
                               device_info.guid));
  }

  void OnGetDeviceId(DetectedPrinter entry,
                     std::string guid,
                     chromeos::UsbPrinterId printer_id) {
    PRINTER_LOG(EVENT) << entry.printer.make_and_model()
                       << " returned USB device ID: " << printer_id.raw_id();

    UpdateSearchDataFromDeviceId(printer_id, &entry);
    entry.ppd_search_data.printer_id = std::move(printer_id);

    // Add detected printer.
    if (session_controller_.IsScreenLocked()) {
      printers_locked_screen_[guid] = entry;
    } else {
      printers_ready_[guid] = entry;
      if (on_printers_found_callback_) {
        on_printers_found_callback_.Run(GetPrinters());
      }
    }
  }

  // device::mojom::UsbDeviceManagerClient implementation.
  void OnDeviceAdded(device::mojom::UsbDeviceInfoPtr device_info) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    DCHECK(device_info);
    DoAddDevice(*device_info);
  }

  // device::mojom::UsbDeviceManagerClient implementation.
  void OnDeviceRemoved(device::mojom::UsbDeviceInfoPtr device_info) override {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
    DCHECK(device_info);
    if (!UsbDeviceIsPrinter(*device_info)) {
      return;
    }
    if (printers_ready_.erase(device_info->guid)) {
      if (on_printers_found_callback_) {
        on_printers_found_callback_.Run(GetPrinters());
      }
    } else {
      printers_locked_screen_.erase(device_info->guid);
    }
  }

  // ash::SessionObserver implementation.
  void OnLockStateChanged(bool locked) override {
    if (locked || printers_locked_screen_.empty()) {
      return;
    }
    printers_ready_.merge(printers_locked_screen_);
    printers_locked_screen_.clear();
    if (on_printers_found_callback_) {
      on_printers_found_callback_.Run(GetPrinters());
    }
  }

  SEQUENCE_CHECKER(sequence_);

  // Map from USB GUID to DetectedPrinter for all detected printers. Printers
  // detected when the screen is locked are saved in `printers_locked_screen_`.
  // They are later moved to `printers_ready_` when the screen is unlocked.
  // This is required because locking the screen activates usbguard that blocks
  // access to USB ports, so we have to defer installation of USB printers.
  std::map<std::string, DetectedPrinter> printers_ready_;
  std::map<std::string, DetectedPrinter> printers_locked_screen_;

  OnPrintersFoundCallback on_printers_found_callback_;

  mojo::Remote<device::mojom::UsbDeviceManager> device_manager_;
  mojo::AssociatedReceiver<device::mojom::UsbDeviceManagerClient>
      client_receiver_{this};
  SessionControllerWrapper session_controller_;
  base::WeakPtrFactory<UsbPrinterDetectorImpl> weak_factory_{this};
};

}  // namespace

// static
std::unique_ptr<UsbPrinterDetector> UsbPrinterDetector::Create() {
  // Bind to the DeviceService for USB device manager.
  mojo::PendingRemote<device::mojom::UsbDeviceManager> usb_manager;
  content::GetDeviceService().BindUsbDeviceManager(
      usb_manager.InitWithNewPipeAndPassReceiver());
  return std::make_unique<UsbPrinterDetectorImpl>(std::move(usb_manager));
}

std::unique_ptr<UsbPrinterDetector> UsbPrinterDetector::CreateForTesting(
    mojo::PendingRemote<device::mojom::UsbDeviceManager> usb_manager,
    ash::SessionController* session_controller) {
  return std::make_unique<UsbPrinterDetectorImpl>(std::move(usb_manager),
                                                  session_controller);
}

}  // namespace ash