chromium/chrome/browser/ash/system_logs/connected_input_devices_log_source_unittest.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 <set>
#include <string>
#include <tuple>
#include <vector>

#include "ash/constants/ash_switches.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/scoped_command_line.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/mojo_service_manager/fake_mojo_service_manager.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/fake_cros_healthd.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/touchpad_device.h"

namespace system_logs {

class ConnectedInputDevicesLogSourceTest : public ::testing::Test {
 public:
  ConnectedInputDevicesLogSourceTest() {}
  ConnectedInputDevicesLogSourceTest(
      const ConnectedInputDevicesLogSourceTest&) = delete;
  ConnectedInputDevicesLogSourceTest& operator=(
      const ConnectedInputDevicesLogSourceTest&) = delete;
  ~ConnectedInputDevicesLogSourceTest() override {
    ash::cros_healthd::FakeCrosHealthd::Shutdown();
    base::RunLoop().RunUntilIdle();
  }

  void SetUp() override {
    ui::DeviceDataManager::CreateInstance();
    ash::cros_healthd::FakeCrosHealthd::Initialize();
  }

  void TearDown() override { ui::DeviceDataManager::DeleteInstance(); }

  void SetCrosHealthdTouchpad(ash::cros_healthd::mojom::TelemetryInfoPtr& info,
                              std::string driver_name) {
    auto input_info = ash::cros_healthd::mojom::InputInfo::New();
    input_info->touchpad_devices =
        std::vector<ash::cros_healthd::mojom::TouchpadDevicePtr>{};

    auto touchpad_device = ash::cros_healthd::mojom::TouchpadDevice::New();
    touchpad_device->driver_name = driver_name;
    auto touchpad_input_device = ash::cros_healthd::mojom::InputDevice::New();
    touchpad_device->input_device = std::move(touchpad_input_device);

    input_info->touchpad_devices->push_back(std::move(touchpad_device));

    info->input_result = ash::cros_healthd::mojom::InputResult::NewInputInfo(
        std::move(input_info));
  }

 protected:
  base::test::TaskEnvironment task_environment_;

  static const uint16_t unknown_vid;
  static const char unknown_vendor_name[];

  size_t num_callback_calls() const { return num_callback_calls_; }
  const SystemLogsResponse& response() const { return response_; }

  SysLogsSourceCallback fetch_callback(base::OnceClosure quit_closure) {
    return base::BindOnce(&ConnectedInputDevicesLogSourceTest::OnFetchComplete,
                          base::Unretained(this), std::move(quit_closure));
  }

  ::ash::mojo_service_manager::FakeMojoServiceManager fake_service_manager_;

  raw_ptr<base::test::ScopedCommandLine> command_line_;

 private:
  void OnFetchComplete(base::OnceClosure quit_closure,
                       std::unique_ptr<SystemLogsResponse> response) {
    ++num_callback_calls_;
    response_ = *response;
    std::move(quit_closure).Run();
  }

  // Used to verify that OnGetFeedbackData was called the correct number of
  // times.
  size_t num_callback_calls_ = 0;

  // Stores results from the log source passed into fetch_callback().
  SystemLogsResponse response_;
};

// Obsolete VID of Fry's Electronics, unlikely to be used.
const uint16_t ConnectedInputDevicesLogSourceTest::unknown_vid = 0x0001;
const char ConnectedInputDevicesLogSourceTest::unknown_vendor_name[] = "0x0001";

TEST_F(ConnectedInputDevicesLogSourceTest, Touchpad_single) {
  const std::string vendor_name = "Synaptics";
  const uint16_t vid = 0x06cb;
  const uint16_t pid = 0x685f;
  const std::string driver_name = "SynPS/2";

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), vid, pid, /*version=*/0)});

  auto telem_info = ash::cros_healthd::mojom::TelemetryInfo::New();

  SetCrosHealthdTouchpad(telem_info, driver_name);
  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(telem_info);

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(3U, response().size());

  auto it = response().find("TOUCHPAD_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(vendor_name, it->second);
  it = response().find("TOUCHPAD_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(driver_name, it->second);

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Flex_touchpad_single) {
  const std::string vendor_name = "Synaptics";
  const uint16_t vid = 0x06cb;
  const uint16_t pid = 0x685f;
  const std::string driver_name = "SynPS/2";
  const std::string flex_library_name = "libinput";

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), vid, pid, /*version=*/0)});

  auto telem_info = ash::cros_healthd::mojom::TelemetryInfo::New();

  SetCrosHealthdTouchpad(telem_info, driver_name);

  telem_info->input_result->get_input_info()->touchpad_library_name =
      flex_library_name;
  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(telem_info);
  command_line_->GetProcessCommandLine()->AppendSwitch(
      ash::switches::kRevenBranding);

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(4U, response().size());

  auto it = response().find("TOUCHPAD_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(vendor_name, it->second);
  it = response().find("TOUCHPAD_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(driver_name, it->second);
  it = response().find("TOUCHPAD_LIBRARY");
  EXPECT_EQ(flex_library_name, it->second);

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchpad_single_other_ext) {
  const std::string t1_vendor_name = "Raydium";
  const uint16_t t1_vid = 0x2386;
  const uint16_t t1_pid = 0x3b1d;
  const uint16_t t2_vid = 0x0457;
  const uint16_t t2_pid = 0x3462;
  const std::string t1_driver_name = "driver1";
  const ui::TouchpadDevice tp1 = ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), t1_vid, t1_pid, /*version=*/0);
  const ui::TouchpadDevice tp2 = ui::TouchpadDevice(
      /*id=*/2, ui::INPUT_DEVICE_USB, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), t2_vid, t2_pid, /*version=*/0);

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({tp1, tp2});

  auto telem_info = ash::cros_healthd::mojom::TelemetryInfo::New();

  // Currently healthD only pulls internal touchpad data, so only that needs
  // to be set
  SetCrosHealthdTouchpad(telem_info, t1_driver_name);
  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(telem_info);

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(3U, response().size());

  auto it = response().find("TOUCHPAD_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(t1_vendor_name, it->second);
  it = response().find("TOUCHPAD_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", t1_pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(t1_driver_name, it->second);

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchpad_single_ts_ext) {
  const std::string tp_vendor_name = "Elan";
  const uint16_t tp_vid = 0x04f3;
  const uint16_t tp_pid = 0x323b;
  const uint16_t ts_vid = 0x056a;
  const uint16_t ts_pid = 0xd4f4;
  const std::string driver_name = "ElanPS/2";
  const ui::TouchpadDevice tp = ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), tp_vid, tp_pid, /*version=*/0);
  const ui::InputDevice ts = ui::InputDevice(
      /*id=*/2, ui::INPUT_DEVICE_BLUETOOTH, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), ts_vid, ts_pid, /*version=*/0);

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({tp});
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices({{ts, gfx::Size(),
                                                         /*touch_points=*/0}});

  auto telem_info = ash::cros_healthd::mojom::TelemetryInfo::New();

  SetCrosHealthdTouchpad(telem_info, driver_name);
  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(telem_info);

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(3U, response().size());

  auto it = response().find("TOUCHPAD_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(tp_vendor_name, it->second);
  it = response().find("TOUCHPAD_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", tp_pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(driver_name, it->second);

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchscreen_single) {
  const std::string vendor_name = "Atmel";
  const uint16_t vid = 0x03eb;
  const uint16_t pid = 0x1234;

  auto input = ui::InputDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), vid, pid, /*version=*/0);
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
      {{input, gfx::Size(), /*touch_points=*/0}});

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(2U, response().size());

  auto it = response().find("TOUCHSCREEN_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(vendor_name, it->second);
  it = response().find("TOUCHSCREEN_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(it, response().end());

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchscreen_single_other_ext) {
  const std::string t1_vendor_name = "Novatek";
  const uint16_t t1_vid = 0x0603;
  const uint16_t t1_pid = 0xd124;
  const uint16_t t2_vid = 0x1fd2;
  const uint16_t t2_pid = 0x0034;

  ui::InputDevice i1 = ui::InputDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), t1_vid, t1_pid, /*version=*/0);
  ui::InputDevice i2 = ui::InputDevice(
      /*id=*/2, ui::INPUT_DEVICE_BLUETOOTH, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), t2_vid, t2_pid, /*version=*/0);
  ui::TouchscreenDevice ts1 =
      ui::TouchscreenDevice(i1, gfx::Size(), /*touch_points=*/0);
  ui::TouchscreenDevice ts2 =
      ui::TouchscreenDevice(i2, gfx::Size(), /*touch_points=*/0);

  ui::DeviceDataManagerTestApi().SetTouchscreenDevices({ts1, ts2});

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(2U, response().size());

  auto it = response().find("TOUCHSCREEN_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(t1_vendor_name, it->second);
  it = response().find("TOUCHSCREEN_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", t1_pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(it, response().end());

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchscreen_single_tp_ext) {
  const uint16_t tp_vid = 0x04f3;
  const uint16_t tp_pid = 0x323b;
  const std::string ts_vendor_name = "Wacom";
  const uint16_t ts_vid = 0x056a;
  const uint16_t ts_pid = 0xd4f4;
  const ui::TouchpadDevice tp = ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_USB, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), tp_vid, tp_pid, /*version=*/0);
  const ui::InputDevice ts = ui::InputDevice(
      /*id=*/2, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), ts_vid, ts_pid, /*version=*/0);

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({tp});
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
      {{ts, gfx::Size(), /*touch_points=*/0}});

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(2U, response().size());

  auto it = response().find("TOUCHSCREEN_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(ts_vendor_name, it->second);
  it = response().find("TOUCHSCREEN_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", ts_pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(it, response().end());

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchpad_single_touchscreen_single) {
  const std::string tp_vendor_name = "Zinitix";
  const uint16_t tp_vid = 0x14e5;
  const uint16_t tp_pid = 0x0901;
  std::string driver_name = "psmouse";
  const std::string ts_vendor_name = "Google";
  const uint16_t ts_vid = 0x18d1;
  const uint16_t ts_pid = 0x5400;

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), tp_vid, tp_pid, /*version=*/0)});

  const auto input = ui::InputDevice(
      /*id=*/2, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), ts_vid, ts_pid, /*version=*/0);
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
      {{input, gfx::Size(), /*touch_points=*/0}});

  auto telem_info = ash::cros_healthd::mojom::TelemetryInfo::New();

  SetCrosHealthdTouchpad(telem_info, driver_name);
  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(telem_info);

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(5U, response().size());

  auto it = response().find("TOUCHPAD_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(tp_vendor_name, it->second);
  it = response().find("TOUCHPAD_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", tp_pid), it->second);
  it = response().find("TOUCHSCREEN_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(ts_vendor_name, it->second);
  it = response().find("TOUCHSCREEN_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", ts_pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(driver_name, it->second);

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchpad_unknown_vendor_single) {
  const uint16_t pid = 0x0002;
  std::string driver_name = "psmouse";

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), unknown_vid, pid, /*version=*/0)});

  auto telem_info = ash::cros_healthd::mojom::TelemetryInfo::New();

  SetCrosHealthdTouchpad(telem_info, driver_name);
  ash::cros_healthd::FakeCrosHealthd::Get()
      ->SetProbeTelemetryInfoResponseForTesting(telem_info);

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(3U, response().size());

  auto it = response().find("TOUCHPAD_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(unknown_vendor_name, it->second);
  it = response().find("TOUCHPAD_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(driver_name, it->second);

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, Touchscreen_unknown_vendor_single) {
  const uint16_t pid = 0x0003;
  const auto input = ui::InputDevice(
      /*id=*/1, ui::INPUT_DEVICE_INTERNAL, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), unknown_vid, pid, /*version=*/0);

  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
      {{input, gfx::Size(), /*touch_points=*/0}});

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(2U, response().size());

  auto it = response().find("TOUCHSCREEN_VENDOR");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(unknown_vendor_name, it->second);
  it = response().find("TOUCHSCREEN_PID");
  ASSERT_NE(it, response().end());
  EXPECT_EQ(base::StringPrintf("0x%04x", pid), it->second);
  it = response().find("TOUCHPAD_DRIVERS");
  EXPECT_EQ(it, response().end());

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

TEST_F(ConnectedInputDevicesLogSourceTest, No_internal_touch_input) {
  const uint16_t tp_vid = 0x03eb;
  const uint16_t tp_pid = 0x1234;
  const uint16_t ts_vid = 0x04b4;
  const uint16_t ts_pid = 0x0763;

  ui::DeviceDataManagerTestApi().SetTouchpadDevices({ui::TouchpadDevice(
      /*id=*/1, ui::INPUT_DEVICE_USB, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), tp_vid, tp_pid, /*version=*/0)});

  const auto input = ui::InputDevice(
      /*id=*/2, ui::INPUT_DEVICE_BLUETOOTH, /*name=*/"", /*phys=*/"",
      /*sys_path=*/base::FilePath(), ts_vid, ts_pid, /*version=*/0);
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(
      {{input, gfx::Size(), /*touch_points=*/0}});

  /* Fetch log data. */
  ConnectedInputDevicesLogSource source;
  base::RunLoop run_loop;
  source.Fetch(fetch_callback(run_loop.QuitClosure()));
  run_loop.Run();

  /* Verify fetch_callback() has been called. */
  EXPECT_EQ(1U, num_callback_calls());
  ASSERT_EQ(0U, response().size());

  /* If size 0, then no point in checking ids. */

  /* Verify fetch_callback() has not been called again. */
  EXPECT_EQ(1U, num_callback_calls());
}

}  // namespace system_logs