chromium/chrome/browser/ash/pcie_peripheral/ash_usb_detector.cc

// Copyright 2021 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/pcie_peripheral/ash_usb_detector.h"

#include <memory>

#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/components/dbus/fwupd/fwupd_client.h"
#include "chromeos/ash/components/fwupd/firmware_update_manager.h"
#include "chromeos/ash/components/peripheral_notification/peripheral_notification_manager.h"
#include "content/public/browser/device_service.h"

namespace ash {

namespace {

static AshUsbDetector* g_ash_usb_detector = nullptr;

constexpr int kRequestUpdatesIntervalInSeconds = 5;
constexpr int kMaxNumRequestUpdatesRetries = 3;

}  // namespace

AshUsbDetector::AshUsbDetector() {
  DCHECK(!g_ash_usb_detector);
  g_ash_usb_detector = this;
  fetch_updates_repeating_timer_ = std::make_unique<base::RepeatingTimer>();
}

AshUsbDetector::~AshUsbDetector() {
  DCHECK_EQ(this, g_ash_usb_detector);
  g_ash_usb_detector = nullptr;
}

// static
AshUsbDetector* AshUsbDetector::Get() {
  return g_ash_usb_detector;
}

void AshUsbDetector::ConnectToDeviceManager() {
  if (!device_manager_) {
    content::GetDeviceService().BindUsbDeviceManager(
        device_manager_.BindNewPipeAndPassReceiver());
  }
  DCHECK(device_manager_);
  device_manager_.set_disconnect_handler(
      base::BindOnce(&AshUsbDetector::OnDeviceManagerConnectionError,
                     weak_ptr_factory_.GetWeakPtr()));

  // Listen for added/removed device events.
  DCHECK(!client_receiver_.is_bound());
  device_manager_->EnumerateDevicesAndSetClient(
      client_receiver_.BindNewEndpointAndPassRemote(),
      base::BindOnce(&AshUsbDetector::OnListAttachedDevices,
                     weak_ptr_factory_.GetWeakPtr()));
}

int32_t AshUsbDetector::GetOnDeviceCheckedCountForTesting() {
  return on_device_checked_counter_for_testing_;
}

void AshUsbDetector::OnDeviceAdded(
    device::mojom::UsbDeviceInfoPtr device_info) {
  std::string guid = device_info->guid;
  device_manager_->CheckAccess(
      guid,
      base::BindOnce(&AshUsbDetector::OnDeviceChecked,
                     weak_ptr_factory_.GetWeakPtr(), std::move(device_info)));

  RequestAllUpdatesWithRepeatDelay();
}

void AshUsbDetector::OnDeviceRemoved(
    device::mojom::UsbDeviceInfoPtr device_info) {
  RequestAllUpdatesWithRepeatDelay();
}

void AshUsbDetector::RequestAllUpdatesWithRepeatDelay() {
  if (!fetch_updates_repeating_timer_->IsRunning()) {
    num_request_updates_repeats_ = kMaxNumRequestUpdatesRetries;
    fetch_updates_repeating_timer_->Start(
        FROM_HERE, base::Seconds(kRequestUpdatesIntervalInSeconds),
        base::BindRepeating(&AshUsbDetector::RequestUpdates,
                            base::Unretained(this)));
  } else {
    // Request to fetch all updates was a called during a current repeat cycle.
    // Reset the number of requests back to the default.
    num_request_updates_repeats_ = kMaxNumRequestUpdatesRetries;
  }
}

void AshUsbDetector::OnListAttachedDevices(
    std::vector<device::mojom::UsbDeviceInfoPtr> devices) {
  for (device::mojom::UsbDeviceInfoPtr& device_info : devices)
    AshUsbDetector::OnDeviceAdded(std::move(device_info));
}

void AshUsbDetector::OnDeviceChecked(
    device::mojom::UsbDeviceInfoPtr device_info,
    bool allowed) {
  if (!allowed)
    return;

  ash::PeripheralNotificationManager::Get()->OnDeviceConnected(
      device_info.get());

  if (is_testing_)
    ++on_device_checked_counter_for_testing_;
}

void AshUsbDetector::OnDeviceManagerConnectionError() {
  device_manager_.reset();
  client_receiver_.reset();
  ConnectToDeviceManager();
}

void AshUsbDetector::RequestUpdates() {
  if (is_testing_) {
    ++num_request_for_fetch_updates_for_testing_;
  } else {
    if (FirmwareUpdateManager::IsInitialized()) {
      FirmwareUpdateManager::Get()->RequestAllUpdates(
          FirmwareUpdateManager::Source::kUSBChange);
    }
  }

  --num_request_updates_repeats_;

  if (num_request_updates_repeats_ == 0) {
    fetch_updates_repeating_timer_->Stop();
  }
}

void AshUsbDetector::SetDeviceManagerForTesting(
    mojo::PendingRemote<device::mojom::UsbDeviceManager> device_manager) {
  DCHECK(!device_manager_) << "device_manager_ was already initialized";
  device_manager_.Bind(std::move(device_manager));
  is_testing_ = true;
}

void AshUsbDetector::SetFetchUpdatesTimerForTesting(
    std::unique_ptr<base::RepeatingTimer> timer) {
  fetch_updates_repeating_timer_ = std::move(timer);
}

}  // namespace ash