chromium/chrome/browser/ash/system_logs/connected_input_devices_log_source.cc

// Copyright 2022 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/system_logs/connected_input_devices_log_source.h"

#include <functional>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>

#include "ash/constants/ash_switches.h"
#include "base/containers/fixed_flat_map.h"
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom-shared.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "connected_input_devices_log_source.h"
#include "ui/events/devices/device_data_manager.h"

namespace system_logs {

namespace {

namespace healthd = ::ash::cros_healthd::mojom;
using healthd::TelemetryInfo;
using healthd::TelemetryInfoPtr;
using ProbeCategories = healthd::ProbeCategoryEnum;

constexpr auto vendor_map = base::MakeFixedFlatMap<uint16_t, std::string_view>({
    {0x03eb, "Atmel"},
    {0x0457, "Silicon Integrated Systems"},
    {0x04b4, "Cypress"},
    {0x04f3, "Elan"},
    {0x056a, "Wacom"},
    {0x0603, "Novatek"},
    {0x06cb, "Synaptics"},
    {0x093a, "Pixart"},
    {0x14e5, "Zinitix"},
    {0x18d1, "Google"},
    {0x1fd2, "Melfas"},
    {0x22c5, "Himax"},
    {0x2386, "Raydium"},
    {0x2575, "Weida"},
    {0x27c6, "Goodix"},
    {0x2a94, "G2 Touch"},
    {0x2c68, "EMRight"},
    {0x2d1f, "Wacom Taiwan"},
});
}  // namespace

ConnectedInputDevicesLogSource::ConnectedInputDevicesLogSource()
    : SystemLogsSource("Input") {}

ConnectedInputDevicesLogSource::~ConnectedInputDevicesLogSource() = default;

void ConnectedInputDevicesLogSource::ProcessDeviceFillResponse(
    const ui::InputDevice dev,
    SystemLogsResponse* response,
    const std::string& vendor_key,
    const std::string& pid_key) {
  DCHECK(response);
  if (dev.type != ui::InputDeviceType::INPUT_DEVICE_INTERNAL)
    return;
  auto it = vendor_map.find(dev.vendor_id);
  std::string vendor_name;
  if (it != vendor_map.end())
    vendor_name.assign(static_cast<std::string>(it->second));
  else
    vendor_name = base::StringPrintf("0x%04x", dev.vendor_id);
  response->emplace(vendor_key, vendor_name);
  response->emplace(pid_key, base::StringPrintf("0x%04x", dev.product_id));
}

void ConnectedInputDevicesLogSource::Fetch(SysLogsSourceCallback callback) {
  DCHECK(!callback.is_null());

  auto response = std::make_unique<SystemLogsResponse>();

  if (ui::DeviceDataManager::HasInstance()) {
    auto* ddm = ui::DeviceDataManager::GetInstance();
    for (auto t : ddm->GetTouchpadDevices())
      ProcessDeviceFillResponse(t, response.get(), "TOUCHPAD_VENDOR",
                                "TOUCHPAD_PID");
    for (auto t : ddm->GetTouchscreenDevices())
      ProcessDeviceFillResponse(t, response.get(), "TOUCHSCREEN_VENDOR",
                                "TOUCHSCREEN_PID");

    bool has_internal_touchpads =
        (response->find("TOUCHPAD_VENDOR") != response->end()) ||
        (response->find("TOUCHPAD_PID") != response->end());

    if (has_internal_touchpads) {
      base::OnceCallback<void(const std::string&, const std::string&)>
          driver_cb = base::BindOnce(
              [](SysLogsSourceCallback sys_callback,
                 std::unique_ptr<SystemLogsResponse> response,
                 const std::string& driver_names,
                 const std::string& touchpad_library_name) {
                DCHECK(response);
                if (!driver_names.empty()) {
                  response->emplace("TOUCHPAD_DRIVERS", driver_names);
                }
                if (ash::switches::IsRevenBranding() &&
                    !touchpad_library_name.empty()) {
                  response->emplace("TOUCHPAD_LIBRARY", touchpad_library_name);
                }
                std::move(sys_callback).Run(std::move(response));
              },
              std::move(callback), std::move(response));

      GetCrosHealthdService()->ProbeTelemetryInfo(
          {ProbeCategories::kInput},
          base::BindOnce(
              &ConnectedInputDevicesLogSource::OnTelemetryInfoProbeResponse,
              weak_ptr_factory_.GetWeakPtr(), std::move(driver_cb)));
    } else {
      std::move(callback).Run(std::move(response));
    }
  }
}

ash::cros_healthd::mojom::CrosHealthdProbeService*
ConnectedInputDevicesLogSource::GetCrosHealthdService() {
  if (!probe_service_ || !probe_service_.is_connected()) {
    ash::cros_healthd::ServiceConnection::GetInstance()->BindProbeService(
        probe_service_.BindNewPipeAndPassReceiver());
    probe_service_.set_disconnect_handler(
        base::BindOnce(&ConnectedInputDevicesLogSource::OnDisconnect,
                       weak_ptr_factory_.GetWeakPtr()));
  }
  return probe_service_.get();
}

void ConnectedInputDevicesLogSource::OnDisconnect() {
  probe_service_.reset();
}

void ConnectedInputDevicesLogSource::OnTelemetryInfoProbeResponse(
    base::OnceCallback<void(const std::string&, const std::string&)> callback,
    TelemetryInfoPtr info_ptr) {
  std::vector<std::string> drivers = {};
  std::string touchpad_library;
  if (!info_ptr->input_result.is_null()) {
    const auto& input_info = info_ptr->input_result->get_input_info();

    if (ash::switches::IsRevenBranding()) {
      touchpad_library = input_info->touchpad_library_name;
    }

    for (const auto& touchpad_device : input_info->touchpad_devices.value()) {
      drivers.push_back(touchpad_device->driver_name);
    }
  } else {
    DVLOG(1) << "InputResult not found in croshealthd response";
  }

  std::move(callback).Run(base::JoinString(drivers, ","), touchpad_library);
}

}  // namespace system_logs