chromium/chromeos/ash/components/peripheral_notification/peripheral_notification_manager.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 "chromeos/ash/components/peripheral_notification/peripheral_notification_manager.h"

#include "ash/constants/ash_features.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "services/device/public/mojom/usb_device.mojom.h"
#include "third_party/cros_system_api/dbus/typecd/dbus-constants.h"

namespace ash {

namespace {

PeripheralNotificationManager* g_instance = nullptr;

const int kBillboardDeviceClassCode = 17;
constexpr char thunderbolt_file_path[] = "/sys/bus/thunderbolt/devices/0-0";

void RecordConnectivityMetric(
    PeripheralNotificationManager::PeripheralConnectivityResults results) {
  base::UmaHistogramEnumeration("Ash.Peripheral.ConnectivityResults", results);
}

// Checks if the board supports Thunderbolt.
bool CheckIfThunderboltFilepathExists(std::string root_prefix) {
  return base::PathExists(base::FilePath(root_prefix + thunderbolt_file_path));
}

}  // namespace

PeripheralNotificationManager::PeripheralNotificationManager(
    bool is_guest_profile,
    bool is_pcie_tunneling_allowed)
    : is_guest_profile_(is_guest_profile),
      is_pcie_tunneling_allowed_(is_pcie_tunneling_allowed) {
  DCHECK(TypecdClient::Get());
  DCHECK(PciguardClient::Get());
  TypecdClient::Get()->AddObserver(this);
  PciguardClient::Get()->AddObserver(this);
}

PeripheralNotificationManager::~PeripheralNotificationManager() {
  TypecdClient::Get()->RemoveObserver(this);
  PciguardClient::Get()->RemoveObserver(this);

  CHECK_EQ(this, g_instance);
  g_instance = nullptr;
}

void PeripheralNotificationManager::AddObserver(Observer* observer) {
  observer_list_.AddObserver(observer);
}

void PeripheralNotificationManager::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

void PeripheralNotificationManager::
    NotifyLimitedPerformancePeripheralReceived() {
  // Show no notifications if PCIe tunneling is allowed.
  if (is_pcie_tunneling_allowed_)
    return;

  for (auto& observer : observer_list_)
    observer.OnLimitedPerformancePeripheralReceived();
}

void PeripheralNotificationManager::NotifyGuestModeNotificationReceived(
    bool is_thunderbolt_only) {
  for (auto& observer : observer_list_)
    observer.OnGuestModeNotificationReceived(is_thunderbolt_only);
}

void PeripheralNotificationManager::NotifyPeripheralBlockedReceived() {
  for (auto& observer : observer_list_)
    observer.OnPeripheralBlockedReceived();
}

void PeripheralNotificationManager::OnBillboardDeviceConnected(
    bool billboard_is_supported) {
  if (!features::IsPcieBillboardNotificationEnabled()) {
    return;
  }

  if (!billboard_is_supported) {
    for (auto& observer : observer_list_)
      observer.OnBillboardDeviceConnected();

    RecordConnectivityMetric(PeripheralConnectivityResults::kBillboardDevice);
  }
}

void PeripheralNotificationManager::OnThunderboltDeviceConnected(
    bool is_thunderbolt_only) {
  if (is_guest_profile_) {
    NotifyGuestModeNotificationReceived(is_thunderbolt_only);
    RecordConnectivityMetric(
        is_thunderbolt_only
            ? PeripheralConnectivityResults::kTBTOnlyAndBlockedInGuestSession
            : PeripheralConnectivityResults::kAltModeFallbackInGuestSession);
    return;
  }

  // Only show notifications if the peripheral is operating at limited
  // performance.
  if (!is_pcie_tunneling_allowed_) {
    NotifyLimitedPerformancePeripheralReceived();
    RecordConnectivityMetric(
        is_thunderbolt_only
            ? PeripheralConnectivityResults::kTBTOnlyAndBlockedByPciguard
            : PeripheralConnectivityResults::kAltModeFallbackDueToPciguard);
    return;
  }

  RecordConnectivityMetric(
      PeripheralConnectivityResults::kTBTSupportedAndAllowed);
}

void PeripheralNotificationManager::OnBlockedThunderboltDeviceConnected(
    const std::string& name) {
  // Currently the device name is not shown in the notification.
  NotifyPeripheralBlockedReceived();
  RecordConnectivityMetric(PeripheralConnectivityResults::kPeripheralBlocked);
}

void PeripheralNotificationManager::OnCableWarning(
    typecd::CableWarningType cable_warning_type) {
  // Decode cable warnging signal.
  switch (cable_warning_type) {
    case typecd::CableWarningType::kInvalidDpCable:
      NotifyInvalidDpCable();
      RecordConnectivityMetric(PeripheralConnectivityResults::kInvalidDpCable);
      break;
    case typecd::CableWarningType::kInvalidUSB4ValidTBTCable:
      NotifyInvalidUSB4ValidTBTCableWarning();
      RecordConnectivityMetric(
          PeripheralConnectivityResults::kInvalidUSB4ValidTBTCable);
      break;
    case typecd::CableWarningType::kInvalidUSB4Cable:
      NotifyInvalidUSB4CableWarning();
      RecordConnectivityMetric(
          PeripheralConnectivityResults::kInvalidUSB4Cable);
      break;
    case typecd::CableWarningType::kInvalidTBTCable:
      NotifyInvalidTBTCableWarning();
      RecordConnectivityMetric(PeripheralConnectivityResults::kInvalidTBTCable);
      break;
    case typecd::CableWarningType::kSpeedLimitingCable:
      NotifySpeedLimitingCableWarning();
      RecordConnectivityMetric(
          PeripheralConnectivityResults::kSpeedLimitingCable);
      break;
    default:
      break;
  }
}

void PeripheralNotificationManager::NotifyInvalidDpCable() {
  for (auto& observer : observer_list_)
    observer.OnInvalidDpCableWarning();
}

void PeripheralNotificationManager::NotifyInvalidUSB4ValidTBTCableWarning() {
  for (auto& observer : observer_list_)
    observer.OnInvalidUSB4ValidTBTCableWarning();
}

void PeripheralNotificationManager::NotifyInvalidUSB4CableWarning() {
  for (auto& observer : observer_list_)
    observer.OnInvalidUSB4CableWarning();
}

void PeripheralNotificationManager::NotifyInvalidTBTCableWarning() {
  for (auto& observer : observer_list_)
    observer.OnInvalidTBTCableWarning();
}

void PeripheralNotificationManager::NotifySpeedLimitingCableWarning() {
  for (auto& observer : observer_list_)
    observer.OnSpeedLimitingCableWarning();
}

void PeripheralNotificationManager::OnDeviceConnected(
    device::mojom::UsbDeviceInfo* device) {
  if (device->class_code == kBillboardDeviceClassCode) {
    // PathExist is a blocking call. PostTask it and wait on the result.
    base::ThreadPool::PostTaskAndReplyWithResult(
        FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
        base::BindOnce(&CheckIfThunderboltFilepathExists, root_prefix_),
        base::BindOnce(
            &PeripheralNotificationManager::OnBillboardDeviceConnected,
            weak_ptr_factory_.GetWeakPtr()));
  }
}

void PeripheralNotificationManager::SetPcieTunnelingAllowedState(
    bool is_pcie_tunneling_allowed) {
  is_pcie_tunneling_allowed_ = is_pcie_tunneling_allowed;
}

void PeripheralNotificationManager::SetRootPrefixForTesting(
    const std::string& prefix) {
  root_prefix_ = prefix;
}

// static
void PeripheralNotificationManager::Initialize(bool is_guest_profile,
                                               bool is_pcie_tunneling_allowed) {
  CHECK(!g_instance);
  g_instance = new PeripheralNotificationManager(is_guest_profile,
                                                 is_pcie_tunneling_allowed);
}

// static
void PeripheralNotificationManager::Shutdown() {
  CHECK(g_instance);
  delete g_instance;
  g_instance = NULL;
}

// static
PeripheralNotificationManager* PeripheralNotificationManager::Get() {
  CHECK(g_instance);
  return g_instance;
}

// static
bool PeripheralNotificationManager::IsInitialized() {
  return g_instance;
}

}  // namespace ash