chromium/ash/webui/diagnostics_ui/backend/input/input_data_provider_unittest.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/webui/diagnostics_ui/backend/input/input_data_provider.h"

#include <cstdint>
#include <iostream>
#include <map>
#include <optional>
#include <vector>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/shell.h"
#include "ash/system/diagnostics/diagnostics_log_controller.h"
#include "ash/system/diagnostics/fake_diagnostics_browser_delegate.h"
#include "ash/system/diagnostics/keyboard_input_log.h"
#include "ash/system/diagnostics/log_test_helpers.h"
#include "ash/test/ash_test_base.h"
#include "ash/webui/diagnostics_ui/backend/input/event_watcher_factory.h"
#include "ash/webui/diagnostics_ui/backend/input/input_data_event_watcher.h"
#include "ash/webui/diagnostics_ui/backend/input/keyboard_input_data_event_watcher.h"
#include "ash/webui/diagnostics_ui/mojom/input_data_provider.mojom.h"
#include "ash/wm/tablet_mode/tablet_mode_controller.h"
#include "base/command_line.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/message_loop/message_pump_for_ui.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/repeating_test_future.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chromeos/ash/components/mojo_service_manager/fake_mojo_service_manager.h"
#include "chromeos/ash/components/system/fake_statistics_provider.h"
#include "chromeos/ash/components/system/statistics_provider.h"
#include "chromeos/ash/components/test/ash_test_suite.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "content/public/test/browser_task_environment.h"
#include "device/udev_linux/fake_udev_loader.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/events/ash/event_rewriter_ash.h"
#include "ui/events/ash/fake_event_rewriter_ash_delegate.h"
#include "ui/events/ash/keyboard_capability.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/keyboard_device.h"
#include "ui/events/devices/touch_device_transform.h"
#include "ui/events/devices/touchscreen_device.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/ozone/device/device_event_observer.h"
#include "ui/events/ozone/device/device_manager.h"
#include "ui/events/ozone/evdev/event_device_test_util.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"

// Note: this is not a recommended pattern, but works and allows cleanly
// formatted invocations for this test set.
#define EXPECT_KEY_EVENTS(observerptr, id, ...)    \
  do {                                             \
    SCOPED_TRACE("EXPECT_KEY_EVENTS invocation");  \
    ExpectKeyEvents(observerptr, id, __VA_ARGS__); \
  } while (0);

namespace ash {
namespace diagnostics {

namespace {

constexpr mojom::TopRowKey kClassicTopRowKeys[] = {
    mojom::TopRowKey::kBack,
    mojom::TopRowKey::kForward,
    mojom::TopRowKey::kRefresh,
    mojom::TopRowKey::kFullscreen,
    mojom::TopRowKey::kOverview,
    mojom::TopRowKey::kScreenBrightnessDown,
    mojom::TopRowKey::kScreenBrightnessUp,
    mojom::TopRowKey::kVolumeMute,
    mojom::TopRowKey::kVolumeDown,
    mojom::TopRowKey::kVolumeUp};

const std::vector<uint32_t> kInternalJinlonScanCodes = {
    0xEA, 0xE7, 0x91, 0x92, 0x93, 0x94, 0x95,
    0x96, 0x97, 0x98, 0xA0, 0xAE, 0xB0};

constexpr mojom::TopRowKey kInternalJinlonTopRowKeys[] = {
    mojom::TopRowKey::kBack,
    mojom::TopRowKey::kRefresh,
    mojom::TopRowKey::kFullscreen,
    mojom::TopRowKey::kOverview,
    mojom::TopRowKey::kScreenshot,
    mojom::TopRowKey::kScreenBrightnessDown,
    mojom::TopRowKey::kScreenBrightnessUp,
    mojom::TopRowKey::kPrivacyScreenToggle,
    mojom::TopRowKey::kKeyboardBacklightDown,
    mojom::TopRowKey::kKeyboardBacklightUp,
    mojom::TopRowKey::kVolumeMute,
    mojom::TopRowKey::kVolumeDown,
    mojom::TopRowKey::kVolumeUp};

constexpr ui::TopRowActionKey kInternalJinlonActionKeys[] = {
    ui::TopRowActionKey::kBack,
    ui::TopRowActionKey::kRefresh,
    ui::TopRowActionKey::kFullscreen,
    ui::TopRowActionKey::kOverview,
    ui::TopRowActionKey::kScreenshot,
    ui::TopRowActionKey::kScreenBrightnessDown,
    ui::TopRowActionKey::kScreenBrightnessUp,
    ui::TopRowActionKey::kPrivacyScreenToggle,
    ui::TopRowActionKey::kKeyboardBacklightDown,
    ui::TopRowActionKey::kKeyboardBacklightUp,
    ui::TopRowActionKey::kVolumeMute,
    ui::TopRowActionKey::kVolumeDown,
    ui::TopRowActionKey::kVolumeUp};

// One possible variant of a Dell configuration
constexpr mojom::TopRowKey kInternalDellTopRowKeys[] = {
    mojom::TopRowKey::kBack,
    mojom::TopRowKey::kRefresh,
    mojom::TopRowKey::kFullscreen,
    mojom::TopRowKey::kOverview,
    mojom::TopRowKey::kScreenBrightnessDown,
    mojom::TopRowKey::kScreenBrightnessUp,
    mojom::TopRowKey::kVolumeMute,
    mojom::TopRowKey::kVolumeDown,
    mojom::TopRowKey::kVolumeUp,
    mojom::TopRowKey::kNone,
    mojom::TopRowKey::kNone,
    mojom::TopRowKey::kScreenMirror,
    mojom::TopRowKey::kDelete};

constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";
constexpr char kKbdTopRowLayoutAttributeName[] = "function_row_physmap";

constexpr char kSillyDeviceName[] = "eventWithoutANumber";

constexpr char kInvalidMechnicalLayout[] = "Not ANSI, JIS, or ISO";

// Privacy Screen replaced with unknown 0xC4 scancode.
constexpr char kModifiedJinlonDescriptor[] =
    "EA E7 91 92 93 94 95 C4 97 98 A0 AE B0";
constexpr uint32_t kUnknownScancode = 0xC4;
constexpr int kUnknownScancodeIndex = 7;

// Device id in DeviceDataManager.
constexpr int kDeviceId1 = 1;
constexpr int kDeviceId2 = 2;

struct KeyDefinition {
  uint32_t key_code;
  uint32_t at_scan_code;
  uint32_t usb_scan_code;
};

// TODO(b/211780758): we should acquire these tuples from dom_code_data.inc,
// where feasible.
constexpr KeyDefinition kKeyA = {KEY_A, 0x1E, 0x70004};
constexpr KeyDefinition kKeyB = {KEY_B, 0x30, 0x70005};
constexpr KeyDefinition kKeyEsc = {KEY_ESC, 0x30, 0x70005};
constexpr KeyDefinition kKeyF1 = {KEY_F1, 0x3B, 0x7003A};
constexpr KeyDefinition kKeyF8 = {KEY_F8, 0x42, 0x70041};
constexpr KeyDefinition kKeyF10 = {KEY_F10, 0x44, 0x70043};
// Drallion AT codes for F11-F12; not standardized
constexpr KeyDefinition kKeyF11 = {KEY_F11, 0x57, 0x700044};
constexpr KeyDefinition kKeyF12 = {KEY_F12, 0xD7, 0x700045};
constexpr KeyDefinition kKeyDelete = {KEY_DELETE, 0xD3, 0x7004C};
// Eve AT code; unknown if this is standard
constexpr KeyDefinition kKeyMenu = {KEY_CONTROLPANEL, 0x5D, 0};
// Jinlon AT code; unknown if this is standard
constexpr KeyDefinition kKeySleep = {KEY_SLEEP, 0x5D, 0};
constexpr KeyDefinition kKeyActionBack = {KEY_BACK, 0xEA, 0x0C0224};
constexpr KeyDefinition kKeyActionRefresh = {KEY_REFRESH, 0xE7, 0x0C0227};
constexpr KeyDefinition kKeyActionFullscreen = {KEY_ZOOM, 0x91, 0x0C0232};
constexpr KeyDefinition kKeyActionOverview = {KEY_SCALE, 0x92, 0x0C029F};
constexpr KeyDefinition kKeyActionScreenshot = {KEY_SYSRQ, 0x93, 0x070046};
constexpr KeyDefinition kKeyActionScreenBrightnessDown = {KEY_BRIGHTNESSDOWN,
                                                          0x94, 0x0C0070};
constexpr KeyDefinition kKeyActionScreenBrightnessUp = {KEY_BRIGHTNESSUP, 0x95,
                                                        0x0C006F};
constexpr KeyDefinition kKeyActionKeyboardBrightnessDown = {KEY_KBDILLUMDOWN,
                                                            0x97, 0x0C007A};
constexpr KeyDefinition kKeyActionKeyboardBrightnessUp = {KEY_KBDILLUMUP, 0x98,
                                                          0x0C0079};
constexpr KeyDefinition kKeyActionKeyboardVolumeMute = {KEY_MUTE, 0xA0,
                                                        0x0C00E2};
constexpr KeyDefinition kKeyActionKeyboardVolumeDown = {KEY_VOLUMEDOWN, 0xAE,
                                                        0x0C00EA};
constexpr KeyDefinition kKeyActionKeyboardVolumeUp = {KEY_VOLUMEUP, 0xB0,
                                                      0x0C00E9};

constexpr uint32_t kKeyboardTesterMetricTimeDelay = 10u;
#if 0
// TODO(b/208729519): Not useful until we can test Drallion keyboards.
// Drallion, no HID equivalent
constexpr KeyDefinition kKeySwitchVideoMode = {KEY_SWITCHVIDEOMODE, 0x8B, 0};
constexpr KeyDefinition kKeyActionPrivacyScreenToggle =
   {KEY_PRIVACY_SCREEN_TOGGLE, 0x96, 0x0C02D0};
#endif

// NOTE: This is only creates a simple ui::InputDevice based on a device
// capabilities report; it is not suitable for subclasses of ui::InputDevice.
ui::InputDevice InputDeviceFromCapabilities(
    int device_id,
    const ui::DeviceCapabilities& capabilities) {
  ui::EventDeviceInfo device_info = {};
  ui::CapabilitiesToDeviceInfo(capabilities, &device_info);

  const std::string sys_path =
      base::StringPrintf("/dev/input/event%d-%s", device_id, capabilities.path);

  return ui::InputDevice(device_id, device_info.device_type(),
                         device_info.name(), device_info.phys(),
                         base::FilePath(sys_path), device_info.vendor_id(),
                         device_info.product_id(), device_info.version());
}

}  // namespace

namespace mojom {

std::ostream& operator<<(std::ostream& os, const KeyEvent& event) {
  os << "KeyEvent{ id=" << event.id << ", ";
  os << "type=" << event.type << ", ";
  os << "key_code=" << event.key_code << ", ";
  os << "scan_code=" << event.scan_code << ", ";
  os << "top_row_position=" << event.top_row_position;
  os << "}";
  return os;
}

}  // namespace mojom

// Fake device manager that lets us control the input devices that
// an InputDataProvider can see.
class FakeDeviceManager : public ui::DeviceManager {
 public:
  FakeDeviceManager() {}
  FakeDeviceManager(const FakeDeviceManager&) = delete;
  FakeDeviceManager& operator=(const FakeDeviceManager&) = delete;
  ~FakeDeviceManager() override {}

  // DeviceManager:
  void ScanDevices(ui::DeviceEventObserver* observer) override {}
  void AddObserver(ui::DeviceEventObserver* observer) override {}
  void RemoveObserver(ui::DeviceEventObserver* observer) override {}
};

class FakeInputDataEventWatcher;
typedef std::map<uint32_t, FakeInputDataEventWatcher*> watchers_t;

// Fake evdev watcher class that lets us manually post input
// events into an InputDataProvider; this keeps an external
// map of watchers updated so that instances can easily be found.
class FakeInputDataEventWatcher : public InputDataEventWatcher {
 public:
  FakeInputDataEventWatcher(
      uint32_t id,
      base::WeakPtr<KeyboardInputDataEventWatcher::Dispatcher> dispatcher,
      watchers_t& watchers)
      : InputDataEventWatcher(id),
        dispatcher_(dispatcher),
        watchers_(watchers) {
    EXPECT_EQ(0u, watchers_->count(this->evdev_id_));
    (*watchers_)[this->evdev_id_] = this;
  }
  ~FakeInputDataEventWatcher() override {
    EXPECT_EQ((*watchers_)[this->evdev_id_], this);
    watchers_->erase(this->evdev_id_);
  }

  void PostKeyEvent(bool down, uint32_t evdev_code, uint32_t scan_code) {
    if (dispatcher_)
      dispatcher_->SendInputKeyEvent(this->evdev_id_, evdev_code, scan_code,
                                     down);
  }

  // ProcessEvent will not be triggered by test code.
  void ProcessEvent(const input_event& event) override {}

  // Only updating boolean instead of watching actual FD. FD watch tested in
  // InputDataEventWatcher unit tests.
  // See: ash/webui/diagnostics_ui/backend/input_data_event_watcher_unittest.cc
  void DoStart() override {}
  void DoStop() override {}

 private:
  base::WeakPtr<KeyboardInputDataEventWatcher::Dispatcher> dispatcher_;
  const raw_ref<watchers_t> watchers_;
};

// Utility to construct FakeInputDataEventWatcher for InputDataProvider.
class FakeInputDataEventWatcherFactory : public EventWatcherFactory {
 public:
  explicit FakeInputDataEventWatcherFactory(watchers_t& watchers)
      : watchers_(watchers) {}
  FakeInputDataEventWatcherFactory(const FakeInputDataEventWatcherFactory&) =
      delete;
  FakeInputDataEventWatcherFactory& operator=(
      const FakeInputDataEventWatcherFactory&) = delete;
  ~FakeInputDataEventWatcherFactory() override = default;

  std::unique_ptr<InputDataEventWatcher> MakeKeyboardEventWatcher(
      uint32_t id,
      base::WeakPtr<KeyboardInputDataEventWatcher::Dispatcher> dispatcher)
      override {
    return std::make_unique<FakeInputDataEventWatcher>(
        id, std::move(dispatcher), *watchers_);
  }

 private:
  const raw_ref<watchers_t> watchers_;
};

// A mock observer that records device change events emitted from an
// InputDataProvider.
class FakeConnectedDevicesObserver : public mojom::ConnectedDevicesObserver {
 public:
  // mojom::ConnectedDevicesObserver:
  void OnTouchDeviceConnected(
      mojom::TouchDeviceInfoPtr new_touch_device) override {
    touch_devices_connected.push_back(std::move(new_touch_device));
  }
  void OnTouchDeviceDisconnected(uint32_t id) override {
    touch_devices_disconnected.push_back(id);
  }
  void OnKeyboardConnected(mojom::KeyboardInfoPtr new_keyboard) override {
    keyboards_connected.push_back(std::move(new_keyboard));
  }
  void OnKeyboardDisconnected(uint32_t id) override {
    keyboards_disconnected.push_back(id);
  }

  std::vector<mojom::TouchDeviceInfoPtr> touch_devices_connected;
  std::vector<uint32_t> touch_devices_disconnected;
  std::vector<mojom::KeyboardInfoPtr> keyboards_connected;
  std::vector<uint32_t> keyboards_disconnected;

  mojo::Receiver<mojom::ConnectedDevicesObserver> receiver{this};
};

// A mock observer that records key event events emitted from an
// InputDataProvider.
class FakeKeyboardObserver : public mojom::KeyboardObserver {
 public:
  enum EventType {
    kEvent = 1,
    kPause = 2,
    kResume = 3,
  };

  // mojom::KeyboardObserver:
  void OnKeyEvent(mojom::KeyEventPtr key_event) override {
    events_.push_back({kEvent, std::move(key_event)});
  }
  void OnKeyEventsPaused() override { events_.push_back({kPause, nullptr}); }
  void OnKeyEventsResumed() override { events_.push_back({kResume, nullptr}); }

  std::vector<std::pair<EventType, mojom::KeyEventPtr>> events_;

  mojo::Receiver<mojom::KeyboardObserver> receiver{this};
};

// A mock observer that records current tablet mode status and counts when
// OnTabletModeChanged function is called.
class FakeTabletModeObserver : public mojom::TabletModeObserver {
 public:
  uint32_t num_tablet_mode_change_calls() const {
    return num_tablet_mode_change_calls_;
  }

  bool is_tablet_mode() { return is_tablet_mode_; }

  // mojom::TabletModeObserver:
  void OnTabletModeChanged(bool is_tablet_mode) override {
    ++num_tablet_mode_change_calls_;
    is_tablet_mode_ = is_tablet_mode;
  }

  mojo::Receiver<mojom::TabletModeObserver> receiver{this};

 private:
  uint32_t num_tablet_mode_change_calls_ = 0;
  bool is_tablet_mode_ = false;
};

class FakeLidStateObserver : public mojom::LidStateObserver {
 public:
  uint32_t num_lid_state_change_calls() const {
    return num_lid_state_change_calls_;
  }

  bool is_lid_open() { return is_lid_open_; }

  // mojom::TabletModeObserver:
  void OnLidStateChanged(bool is_lid_open) override {
    ++num_lid_state_change_calls_;
    is_lid_open_ = is_lid_open;
  }

  mojo::Receiver<mojom::LidStateObserver> receiver{this};

 private:
  uint32_t num_lid_state_change_calls_ = 0;
  bool is_lid_open_ = false;
};

// A mock observer that records current internal display power state and counts
// when OnInternalDisplayPowerStateChanged function is called.
class FakeInternalDisplayPowerStateObserver
    : public mojom::InternalDisplayPowerStateObserver {
 public:
  uint32_t num_display_state_change_calls() const {
    return num_display_state_change_calls_;
  }

  bool is_display_on() { return is_display_on_; }

  // mojom::InternalDisplayPowerStateObserver:
  void OnInternalDisplayPowerStateChanged(bool is_display_on) override {
    ++num_display_state_change_calls_;
    is_display_on_ = is_display_on;
  }

  mojo::Receiver<mojom::InternalDisplayPowerStateObserver> receiver{this};

 private:
  uint32_t num_display_state_change_calls_ = 0;
  bool is_display_on_ = true;
};

// A utility class that fakes obtaining information about an evdev.
class FakeInputDeviceInfoHelper : public InputDeviceInfoHelper {
 public:
  FakeInputDeviceInfoHelper() {}

  ~FakeInputDeviceInfoHelper() override {}

  std::unique_ptr<InputDeviceInformation> GetDeviceInfo(
      int id,
      base::FilePath path) override {
    ui::DeviceCapabilities device_caps;
    const std::string base_name = path.BaseName().value();
    auto info = std::make_unique<InputDeviceInformation>();
    std::unique_ptr<ui::KeyboardCapability::KeyboardInfo> keyboard_info;

    if (base_name == "event0") {
      device_caps = ui::kLinkKeyboard;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout1;
      EXPECT_EQ(0, id);
    } else if (base_name == "event1") {
      device_caps = ui::kLinkTouchpad;
      EXPECT_EQ(1, id);
    } else if (base_name == "event2") {
      device_caps = ui::kKohakuTouchscreen;
      EXPECT_EQ(2, id);
    } else if (base_name == "event3") {
      device_caps = ui::kKohakuStylus;
      EXPECT_EQ(3, id);
    } else if (base_name == "event4") {
      device_caps = ui::kHpUsbKeyboard;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDefault;
      EXPECT_EQ(4, id);
    } else if (base_name == "event5") {
      device_caps = ui::kSarienKeyboard;  // Wilco
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutWilco;
      EXPECT_EQ(5, id);
    } else if (base_name == "event6") {
      device_caps = ui::kEveKeyboard;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout2;
      EXPECT_EQ(6, id);
    } else if (base_name == "event7") {
      device_caps = ui::kJinlonKeyboard;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom;
      info->keyboard_scan_codes = kInternalJinlonScanCodes;

      keyboard_info = std::make_unique<ui::KeyboardCapability::KeyboardInfo>();
      keyboard_info->device_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      keyboard_info->top_row_action_keys.assign(
          std::begin(kInternalJinlonActionKeys),
          std::end(kInternalJinlonActionKeys));
      keyboard_info->top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom;
      keyboard_info->top_row_scan_codes = kInternalJinlonScanCodes;
      EXPECT_EQ(7, id);
    } else if (base_name == "event8") {
      device_caps = ui::kMicrosoftBluetoothNumberPad;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDefault;
      EXPECT_EQ(8, id);
    } else if (base_name == "event9") {
      device_caps = ui::kLogitechTouchKeyboardK400;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceExternalGenericKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutDefault;
      EXPECT_EQ(9, id);
    } else if (base_name == "event10") {
      device_caps = ui::kDrallionKeyboard;
      EXPECT_EQ(10, id);
    } else if (base_name == "event11") {
      // Used for customized top row layout.
      device_caps = ui::kJinlonKeyboard;
      device_caps.kbd_function_row_physmap = kModifiedJinlonDescriptor;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom;
      info->keyboard_scan_codes = kInternalJinlonScanCodes;
      // Set 0xC4 to be F8.
      info->keyboard_scan_codes[7] = 0xC4;

      keyboard_info = std::make_unique<ui::KeyboardCapability::KeyboardInfo>();
      keyboard_info->device_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      keyboard_info->top_row_action_keys.assign(
          std::begin(kInternalJinlonActionKeys),
          std::end(kInternalJinlonActionKeys));
      keyboard_info->top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom;
      keyboard_info->top_row_scan_codes = kInternalJinlonScanCodes;
      keyboard_info->top_row_scan_codes[7] = 0xC4;
      keyboard_info->top_row_action_keys[7] = ui::TopRowActionKey::kUnknown;
      EXPECT_EQ(11, id);
    } else if (base_name == "event12") {
      device_caps = ui::kMorphiusTabletModeSwitch;
      EXPECT_EQ(12, id);
    } else if (base_name == "event13") {
      device_caps = ui::kHammerKeyboard;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayout2;
      EXPECT_EQ(13, id);
    } else if (base_name == "event14") {
      device_caps = ui::kBaskingTouchScreen;
      EXPECT_EQ(14, id);
    } else if (base_name == "event15") {
      device_caps = ui::kSplitModifierKeyboard;
      info->keyboard_type =
          ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard;
      info->keyboard_top_row_layout =
          ui::KeyboardCapability::KeyboardTopRowLayout::kKbdTopRowLayoutCustom;
      EXPECT_EQ(15, id);
    } else if (base_name == kSillyDeviceName) {
      // Simulate a device that is properly described, but has a malformed
      // device name.
      EXPECT_EQ(98, id);
      device_caps = ui::kLinkKeyboard;
    } else if (base_name == "event99") {
      EXPECT_EQ(99, id);
      // Simulate a device that couldn't be opened or have its info determined
      // for whatever reason.
      return nullptr;
    }

    EXPECT_TRUE(
        ui::CapabilitiesToDeviceInfo(device_caps, &info->event_device_info));
    info->evdev_id = id;
    info->path = path;
    info->input_device =
        InputDeviceFromCapabilities(info->evdev_id, device_caps);
    info->connection_type =
        InputDataProvider::ConnectionTypeFromInputDeviceType(
            info->event_device_info.device_type());

    if (keyboard_info) {
      Shell::Get()
          ->keyboard_capability()
          ->DisableKeyboardInfoTrimmingForTesting();
      Shell::Get()->keyboard_capability()->SetKeyboardInfoForTesting(
          ui::KeyboardDevice(info->input_device), std::move(*keyboard_info));
    }

    return info;
  }
};

// Our modifications to InputDataProvider that carries around its own
// widget (representing the window that needs to be visible for key events
// to be observed), the needed factories for our fake utilities, and a
// reference to the current event watchers.
class TestInputDataProvider : public InputDataProvider {
 public:
  TestInputDataProvider(views::Widget* widget,
                        watchers_t& watchers,
                        ui::EventRewriterAsh::Delegate* event_rewriter_delegate)
      : InputDataProvider(
            widget->GetNativeWindow(),
            std::make_unique<FakeDeviceManager>(),
            std::make_unique<FakeInputDataEventWatcherFactory>(watchers),
            Shell::Get()->accelerator_controller(),
            event_rewriter_delegate),
        attached_widget_(widget),
        watchers_(watchers) {
    info_helper_ = base::SequenceBound<FakeInputDeviceInfoHelper>(
        base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}));
  }
  explicit TestInputDataProvider(const TestInputDataProvider&) = delete;
  TestInputDataProvider& operator=(const TestInputDataProvider&) = delete;

  // The widget represents the tab that input diagnostics would normally be
  // shown in. This is allocated outside this class so it won't
  // be destroyed early. (See next item.)
  raw_ptr<views::Widget> attached_widget_;
  // Keep a list of watchers for each evdev in the provider. This is a
  // reference to an instance outside of this class, as the lifetime of the
  // list needs to exceed the destruction of this test class, and can only be
  // cleaned up once all watchers have been destroyed by the base
  // InputDataProvider, which occurs after our destruction.
  const raw_ref<watchers_t> watchers_;
};

class InputDataProviderTest : public AshTestBase {
 public:
  InputDataProviderTest()
      : AshTestBase(content::BrowserTaskEnvironment::TimeSource::MOCK_TIME) {}

  InputDataProviderTest(const InputDataProviderTest&) = delete;
  InputDataProviderTest& operator=(const InputDataProviderTest&) = delete;
  ~InputDataProviderTest() override = default;

  void SetUp() override {
    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
    scoped_feature_list_->InitAndEnableFeature(
        features::kEnableExternalKeyboardsInDiagnostics);

    ui::ResourceBundle::CleanupSharedInstance();
    AshTestSuite::LoadTestResources();
    AshTestBase::SetUp();

    // Note: some init for creating widgets is performed in base SetUp
    // instead of the constructor, so our init must also be delayed until
    // SetUp, so we can safely invoke CreateTestWidget().

    statistics_provider_.SetMachineStatistic(
        system::kKeyboardMechanicalLayoutKey, "ANSI");
    system::StatisticsProvider::SetTestProvider(&statistics_provider_);

    fake_udev_ = std::make_unique<testing::FakeUdevLoader>();
    widget_ =
        CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
    provider_ = std::make_unique<TestInputDataProvider>(
        widget_.get(), watchers_, &event_rewriter_delegate_);
    DiagnosticsLogController::Initialize(
        std::make_unique<FakeDiagnosticsBrowserDelegate>());

    // Apply these early, in SetUp; delaying until
    // FakeInputDeviceInfoHelper::GetDeviceInfo() is not appropriate, as
    // fake_udev is not thread safe. (If multiple devices are constructed in a
    // row, then GetDeviceInfo() invocation can overlap with
    // ProcessInputDataProvider::ProcessDeviceInfo() which reads from udev).
    UdevAddFakeDeviceCapabilities("/dev/input/event5", ui::kSarienKeyboard);
    UdevAddFakeDeviceCapabilities("/dev/input/event6", ui::kEveKeyboard);
    UdevAddFakeDeviceCapabilities("/dev/input/event7", ui::kJinlonKeyboard);
    UdevAddFakeDeviceCapabilities("/dev/input/event10", ui::kDrallionKeyboard);
    // Tweak top row keys for event11.
    auto device_caps = ui::kJinlonKeyboard;
    device_caps.kbd_function_row_physmap = kModifiedJinlonDescriptor;
    UdevAddFakeDeviceCapabilities("/dev/input/event11", device_caps);
  }

  void TearDown() override {
    provider_.reset();
    base::RunLoop().RunUntilIdle();
    AshTestBase::TearDown();
  }

  bool OpenAndCloseLauncher() {
    const auto launcher_accelerator =
        ui::Accelerator(ui::VKEY_ALL_APPLICATIONS, ui::EF_NONE,
                        ui::Accelerator::KeyState::PRESSED);

    // Open and close the launcher
    return Shell::Get()->accelerator_controller()->AcceleratorPressed(
               launcher_accelerator) &&
           Shell::Get()->accelerator_controller()->AcceleratorPressed(
               launcher_accelerator);
  }

  bool ModifierRewritesAreSuppressed() {
    return !event_rewriter_delegate_.RewriteModifierKeys();
  }

 protected:
  struct ExpectedKeyEvent {
    KeyDefinition key;
    int position;
    bool down = true;
  };

  void ExpectKeyEvents(FakeKeyboardObserver* fake_observer,
                       uint32_t id,
                       std::initializer_list<ExpectedKeyEvent> list) {
    // Make sure the test does something...
    EXPECT_TRUE(std::size(list) > 0);

    size_t i;

    i = 0;
    for (auto* iter = list.begin(); iter != list.end(); iter++, i++) {
      (*provider_->watchers_)[id]->PostKeyEvent(iter->down, iter->key.key_code,
                                                iter->key.at_scan_code);
    }
    base::RunLoop().RunUntilIdle();

    ASSERT_EQ(std::size(list), fake_observer->events_.size());

    i = 0;
    for (auto* iter = list.begin(); iter != list.end(); iter++, i++) {
      EXPECT_EQ(
          *fake_observer->events_[i].second,
          mojom::KeyEvent(/*id=*/id,
                          /*type=*/iter->down ? mojom::KeyEventType::kPress
                                              : mojom::KeyEventType::kRelease,
                          /*key_code=*/iter->key.key_code,
                          /*scan_code=*/iter->key.at_scan_code,
                          /*top_row_position=*/iter->position))
          << " which is EXPECT_KEY_EVENTS item #" << i;
    }
  }

  void UdevAddFakeDeviceCapabilities(
      const std::string& device_name,
      const ui::DeviceCapabilities& device_caps) {
    std::map<std::string, std::string>
        sysfs_properties;  // Old style numeric tags.
    std::map<std::string, std::string>
        sysfs_attributes;  // New style vivaldi scancode layouts.

    if (device_caps.kbd_function_row_physmap &&
        strlen(device_caps.kbd_function_row_physmap) > 0) {
      sysfs_attributes[kKbdTopRowLayoutAttributeName] =
          device_caps.kbd_function_row_physmap;
    }

    if (device_caps.kbd_top_row_layout &&
        strlen(device_caps.kbd_top_row_layout) > 0) {
      sysfs_properties[kKbdTopRowPropertyName] = device_caps.kbd_top_row_layout;
    }

    // Each device needs a unique sys path; many of the ones embedded in
    // capabilities are the same, so uniquify them with the event device name.
    // These aren't actual valid paths, but nothing in the testing logic needs
    // them to be real.
    const std::string sys_path = device_name + "-" + device_caps.path;

    fake_udev_->AddFakeDevice(device_caps.name, sys_path.c_str(),
                              /*subsystem=*/"input", /*devnode=*/std::nullopt,
                              /*devtype=*/std::nullopt,
                              std::move(sysfs_attributes),
                              std::move(sysfs_properties));
  }

  ash::mojo_service_manager::FakeMojoServiceManager fake_service_manager_;
  std::unique_ptr<testing::FakeUdevLoader> fake_udev_;
  system::FakeStatisticsProvider statistics_provider_;
  std::unique_ptr<views::Widget> widget_;
  // All evdev watchers in use by provider_.
  watchers_t watchers_;
  ui::test::FakeEventRewriterAshDelegate event_rewriter_delegate_;
  std::unique_ptr<TestInputDataProvider> provider_;

 private:
  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
};

TEST_F(InputDataProviderTest, GetConnectedDevices_DeviceInfoMapping) {
  ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event0"));
  ui::DeviceEvent event1(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event1"));
  ui::DeviceEvent event2(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event2"));
  ui::DeviceEvent event3(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event3"));
  provider_->OnDeviceEvent(event0);
  provider_->OnDeviceEvent(event1);
  provider_->OnDeviceEvent(event2);
  provider_->OnDeviceEvent(event3);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();
  const auto& touch_devices = future.Get<1>();

  ASSERT_EQ(1ul, keyboards.size());
  // The stylus device should be filtered out, hence only 2 touch devices.
  ASSERT_EQ(2ul, touch_devices.size());

  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
  EXPECT_EQ(0u, keyboard->id);
  EXPECT_EQ(mojom::ConnectionType::kInternal, keyboard->connection_type);
  EXPECT_EQ("AT Translated Set 2 keyboard", keyboard->name);

  const mojom::TouchDeviceInfoPtr& touchpad = touch_devices[0];
  EXPECT_EQ(1u, touchpad->id);
  EXPECT_EQ(mojom::ConnectionType::kInternal, touchpad->connection_type);
  EXPECT_EQ(mojom::TouchDeviceType::kPointer, touchpad->type);
  EXPECT_EQ("Atmel maXTouch Touchpad", touchpad->name);

  const mojom::TouchDeviceInfoPtr& touchscreen = touch_devices[1];
  EXPECT_EQ(2u, touchscreen->id);
  EXPECT_EQ(mojom::ConnectionType::kInternal, touchscreen->connection_type);
  EXPECT_EQ(mojom::TouchDeviceType::kDirect, touchscreen->type);
  EXPECT_EQ("Atmel maXTouch Touchscreen", touchscreen->name);
}

TEST_F(InputDataProviderTest, GetConnectedDevices_HasInternalKeyboard) {
  // Initialize one internal keyboard in DeviceDataManager.
  std::vector<ui::KeyboardDevice> keyboard_devices;
  keyboard_devices.push_back(
      ui::KeyboardDevice(kDeviceId1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
                         "Internal Keyboard"));
  ui::DeviceDataManagerTestApi().SetKeyboardDevices(keyboard_devices);

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  // The return values are supposed to be not ready since GetConnectedDevices()
  // function will wait for the internal keyboard to be added.
  ASSERT_FALSE(future.IsReady());

  // Add an internal keyboard.
  ui::DeviceEvent event(ui::DeviceEvent::DeviceType::INPUT,
                        ui::DeviceEvent::ActionType::ADD,
                        base::FilePath("/dev/input/event5"));
  provider_->OnDeviceEvent(event);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(future.IsReady());
  const auto& keyboards = future.Get<0>();
  ASSERT_EQ(1ul, keyboards.size());
}

TEST_F(InputDataProviderTest, GetConnectedDevices_SplitModifierKeyboard) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kModifierSplit);
  auto ignore_modifier_split_secret_key =
      ash::switches::SetIgnoreModifierSplitSecretKeyForTest();

  Shell::Get()
      ->keyboard_capability()
      ->ResetModifierSplitDogfoodControllerForTesting();

  // Initialize one split modifier keyboard in DeviceDataManager.
  std::vector<ui::KeyboardDevice> keyboard_devices;
  keyboard_devices.emplace_back(
      kDeviceId1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
      "Split Modifier Keyboard", /*has_assistant_key=*/true,
      /*has_function_key=*/true);
  ui::DeviceDataManagerTestApi().SetKeyboardDevices(keyboard_devices);

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  // The return values are supposed to be ready since GetConnectedDevices()
  // function won't wait for the split modifier keyboard to be added.
  ASSERT_TRUE(future.IsReady());
}

TEST_F(InputDataProviderTest, FilterOutSplitModifierKeyboard) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(features::kModifierSplit);
  auto ignore_modifier_split_secret_key =
      ash::switches::SetIgnoreModifierSplitSecretKeyForTest();

  Shell::Get()
      ->keyboard_capability()
      ->ResetModifierSplitDogfoodControllerForTesting();

  // Initialize one split modifier keyboard in DeviceDataManager.
  std::vector<ui::KeyboardDevice> keyboard_devices;
  keyboard_devices.emplace_back(
      kDeviceId1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
      "Split Modifier Keyboard", /*has_assistant_key=*/true,
      /*has_function_key=*/true);
  ui::DeviceDataManagerTestApi().SetKeyboardDevices(keyboard_devices);

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  // Add an split modifier keyboard.
  ui::DeviceEvent event(ui::DeviceEvent::DeviceType::INPUT,
                        ui::DeviceEvent::ActionType::ADD,
                        base::FilePath("/dev/input/event15"));
  provider_->OnDeviceEvent(event);
  base::RunLoop().RunUntilIdle();

  const auto& keyboards = future.Get<0>();
  ASSERT_EQ(0ul, keyboards.size());
}

TEST_F(InputDataProviderTest, GetConnectedDevices_AddEventAfterFirstCall) {
  {
    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                           std::vector<mojom::TouchDeviceInfoPtr>>
        future;
    provider_->GetConnectedDevices(future.GetCallback());

    const auto& keyboards = future.Get<0>();
    const auto& touch_devices = future.Get<1>();
    ASSERT_EQ(0ul, keyboards.size());
    ASSERT_EQ(0ul, touch_devices.size());
  }

  ui::DeviceEvent event(ui::DeviceEvent::DeviceType::INPUT,
                        ui::DeviceEvent::ActionType::ADD,
                        base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(event);
  base::RunLoop().RunUntilIdle();

  {
    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                           std::vector<mojom::TouchDeviceInfoPtr>>
        future;
    provider_->GetConnectedDevices(future.GetCallback());

    const auto& keyboards = future.Get<0>();
    const auto& touch_devices = future.Get<1>();

    ASSERT_EQ(1ul, keyboards.size());
    const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
    EXPECT_EQ(4u, keyboard->id);
    EXPECT_EQ(mojom::ConnectionType::kUsb, keyboard->connection_type);
    EXPECT_EQ("Chicony HP Elite USB Keyboard", keyboard->name);

    EXPECT_EQ(0ul, touch_devices.size());
  }
}

TEST_F(InputDataProviderTest, GetConnectedDevices_AddUnusualDevices) {
  // Add two devices with unusual bus types, and verify connection types.

  ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event8"));
  ui::DeviceEvent event1(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event9"));
  provider_->OnDeviceEvent(event0);
  provider_->OnDeviceEvent(event1);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();
  const auto& touch_devices = future.Get<1>();

  ASSERT_EQ(2ul, keyboards.size());
  ASSERT_EQ(0ul, touch_devices.size());

  const mojom::KeyboardInfoPtr& keyboard1 = keyboards[0];
  EXPECT_EQ(8u, keyboard1->id);
  EXPECT_EQ(mojom::ConnectionType::kBluetooth, keyboard1->connection_type);
  EXPECT_EQ(ui::kMicrosoftBluetoothNumberPad.name, keyboard1->name);

  const mojom::KeyboardInfoPtr& keyboard2 = keyboards[1];
  EXPECT_EQ(9u, keyboard2->id);
  EXPECT_EQ(mojom::ConnectionType::kUnknown, keyboard2->connection_type);
  EXPECT_EQ(ui::kLogitechTouchKeyboardK400.name, keyboard2->name);
}

TEST_F(InputDataProviderTest, GetConnectedDevices_Remove) {
  ui::DeviceEvent add_touch_event(ui::DeviceEvent::DeviceType::INPUT,
                                  ui::DeviceEvent::ActionType::ADD,
                                  base::FilePath("/dev/input/event1"));
  provider_->OnDeviceEvent(add_touch_event);
  ui::DeviceEvent add_kbd_event(ui::DeviceEvent::DeviceType::INPUT,
                                ui::DeviceEvent::ActionType::ADD,
                                base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(add_kbd_event);
  base::RunLoop().RunUntilIdle();

  {
    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                           std::vector<mojom::TouchDeviceInfoPtr>>
        future;
    provider_->GetConnectedDevices(future.GetCallback());

    const auto& keyboards = future.Get<0>();
    const auto& touch_devices = future.Get<1>();

    ASSERT_EQ(1ul, keyboards.size());
    EXPECT_EQ(4u, keyboards[0]->id);

    ASSERT_EQ(1ul, touch_devices.size());
    EXPECT_EQ(1u, touch_devices[0]->id);
  }

  ui::DeviceEvent remove_touch_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::REMOVE,
                                     base::FilePath("/dev/input/event1"));
  provider_->OnDeviceEvent(remove_touch_event);
  ui::DeviceEvent remove_kbd_event(ui::DeviceEvent::DeviceType::INPUT,
                                   ui::DeviceEvent::ActionType::REMOVE,
                                   base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(remove_kbd_event);
  base::RunLoop().RunUntilIdle();

  {
    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                           std::vector<mojom::TouchDeviceInfoPtr>>
        future;
    provider_->GetConnectedDevices(future.GetCallback());

    const auto& keyboards = future.Get<0>();
    const auto& touch_devices = future.Get<1>();

    EXPECT_EQ(0ul, keyboards.size());
    EXPECT_EQ(0ul, touch_devices.size());
  }
}

TEST_F(InputDataProviderTest, GetConnectedDevices_NoExternalKeyboards) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kEnableExternalKeyboardsInDiagnostics);

  ui::DeviceEvent add_internal_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::ADD,
                                     base::FilePath("/dev/input/event0"));
  ui::DeviceEvent add_external_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::ADD,
                                     base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(add_internal_event);
  provider_->OnDeviceEvent(add_external_event);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& internal_kbd = keyboards[0];
  EXPECT_EQ(0u, internal_kbd->id);
  EXPECT_EQ(mojom::ConnectionType::kInternal, internal_kbd->connection_type);
}

TEST_F(InputDataProviderTest, KeyboardPhysicalLayoutDetection) {
  statistics_provider_.SetMachineStatistic(system::kKeyboardMechanicalLayoutKey,
                                           "ISO");

  ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event0"));
  ui::DeviceEvent event1(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event4"));
  ui::DeviceEvent event2(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event5"));
  ui::DeviceEvent event3(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event7"));
  provider_->OnDeviceEvent(event0);
  provider_->OnDeviceEvent(event1);
  provider_->OnDeviceEvent(event2);
  provider_->OnDeviceEvent(event3);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(4ul, keyboards.size());

  const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
  EXPECT_EQ(0u, builtin_keyboard->id);
  EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
            builtin_keyboard->physical_layout);
  EXPECT_EQ(mojom::MechanicalLayout::kIso, builtin_keyboard->mechanical_layout);
  EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
            builtin_keyboard->number_pad_present);
  EXPECT_EQ(
      std::vector(std::begin(kClassicTopRowKeys), std::end(kClassicTopRowKeys)),
      builtin_keyboard->top_row_keys);

  const mojom::KeyboardInfoPtr& external_keyboard = keyboards[1];
  EXPECT_EQ(4u, external_keyboard->id);
  EXPECT_EQ(mojom::PhysicalLayout::kUnknown,
            external_keyboard->physical_layout);
  EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
            external_keyboard->mechanical_layout);
  EXPECT_EQ(mojom::NumberPadPresence::kUnknown,
            external_keyboard->number_pad_present);
  EXPECT_EQ(
      std::vector(std::begin(kClassicTopRowKeys), std::end(kClassicTopRowKeys)),
      external_keyboard->top_row_keys);

  const mojom::KeyboardInfoPtr& dell_internal_keyboard = keyboards[2];
  EXPECT_EQ(5u, dell_internal_keyboard->id);
  EXPECT_EQ(mojom::PhysicalLayout::kChromeOSDellEnterpriseWilco,
            dell_internal_keyboard->physical_layout);
  EXPECT_EQ(mojom::MechanicalLayout::kIso,
            dell_internal_keyboard->mechanical_layout);
  EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
            dell_internal_keyboard->number_pad_present);
  EXPECT_EQ(std::vector(std::begin(kInternalDellTopRowKeys),
                        std::end(kInternalDellTopRowKeys)),
            dell_internal_keyboard->top_row_keys);

  const mojom::KeyboardInfoPtr& jinlon_internal_keyboard = keyboards[3];
  EXPECT_EQ(7u, jinlon_internal_keyboard->id);
  EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
            jinlon_internal_keyboard->physical_layout);
  EXPECT_EQ(mojom::MechanicalLayout::kIso,
            jinlon_internal_keyboard->mechanical_layout);
  EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
            jinlon_internal_keyboard->number_pad_present);
  EXPECT_EQ(std::vector(std::begin(kInternalJinlonTopRowKeys),
                        std::end(kInternalJinlonTopRowKeys)),
            jinlon_internal_keyboard->top_row_keys);

  // TODO(b/208729519): We should check a Drallion keyboard, however that
  // invokes a check through the global Shell that does not operate in
  // this test.
}

TEST_F(InputDataProviderTest, KeyboardRegionDetection) {
  statistics_provider_.SetMachineStatistic(system::kRegionKey, "jp");

  ui::DeviceEvent event_internal(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event0"));
  ui::DeviceEvent event_external(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(event_internal);
  provider_->OnDeviceEvent(event_external);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(2ul, keyboards.size());

  const mojom::KeyboardInfoPtr& internal_keyboard = keyboards[0];
  EXPECT_EQ("jp", internal_keyboard->region_code);

  const mojom::KeyboardInfoPtr& external_keyboard = keyboards[1];
  EXPECT_EQ(std::nullopt, external_keyboard->region_code);
}

TEST_F(InputDataProviderTest, KeyboardRegionDetection_Failure) {
  statistics_provider_.ClearMachineStatistic(system::kRegionKey);

  ui::DeviceEvent event_internal(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event0"));
  provider_->OnDeviceEvent(event_internal);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& internal_keyboard = keyboards[0];
  EXPECT_EQ(std::nullopt, internal_keyboard->region_code);
}

TEST_F(InputDataProviderTest, KeyboardAssistantKeyDetection) {
  ui::DeviceEvent link_event(ui::DeviceEvent::DeviceType::INPUT,
                             ui::DeviceEvent::ActionType::ADD,
                             base::FilePath("/dev/input/event0"));
  ui::DeviceEvent eve_event(ui::DeviceEvent::DeviceType::INPUT,
                            ui::DeviceEvent::ActionType::ADD,
                            base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(link_event);
  provider_->OnDeviceEvent(eve_event);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());
  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(2ul, keyboards.size());

  const mojom::KeyboardInfoPtr& link_keyboard = keyboards[0];
  EXPECT_EQ(0u, link_keyboard->id);
  EXPECT_FALSE(link_keyboard->has_assistant_key);
  const mojom::KeyboardInfoPtr& eve_keyboard = keyboards[1];
  EXPECT_EQ(6u, eve_keyboard->id);
  EXPECT_TRUE(eve_keyboard->has_assistant_key);
}

TEST_F(InputDataProviderTest, KeyboardNumberPadDetectionInternal) {
  // Detection of internal number pad depends on command-line
  // argument, and is not a property of the keyboard device.

  base::CommandLine::ForCurrentProcess()->InitFromArgv(
      {"", "--has-number-pad"});
  ui::DeviceEvent link_event(ui::DeviceEvent::DeviceType::INPUT,
                             ui::DeviceEvent::ActionType::ADD,
                             base::FilePath("/dev/input/event0"));
  provider_->OnDeviceEvent(link_event);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());
  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
  EXPECT_EQ(0u, builtin_keyboard->id);
  EXPECT_EQ(mojom::NumberPadPresence::kPresent,
            builtin_keyboard->number_pad_present);
}

TEST_F(InputDataProviderTest, KeyboardTopRightKey_Clamshell) {
  // Devices without a tablet mode switch should be assumed to be clamshells,
  // with a power key in the top-right.
  ui::DeviceEvent event_keyboard(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event0"));
  provider_->OnDeviceEvent(event_keyboard);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
  EXPECT_EQ(mojom::TopRightKey::kPower, keyboard->top_right_key);
}

TEST_F(InputDataProviderTest, KeyboardTopRightKey_Convertible_ModeSwitchFirst) {
  // Devices with a tablet mode switch should be assumed to be convertibles,
  // with a lock key in the top-right.
  ui::DeviceEvent event_mode_switch(ui::DeviceEvent::DeviceType::INPUT,
                                    ui::DeviceEvent::ActionType::ADD,
                                    base::FilePath("/dev/input/event12"));
  ui::DeviceEvent event_keyboard(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event11"));
  provider_->OnDeviceEvent(event_mode_switch);
  provider_->OnDeviceEvent(event_keyboard);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
  EXPECT_EQ(mojom::TopRightKey::kLock, keyboard->top_right_key);
}

TEST_F(InputDataProviderTest, KeyboardTopRightKey_Convertible_KeyboardFirst) {
  // Devices with a tablet mode switch should be assumed to be convertibles,
  // with a lock key in the top-right, even if we get the tablet mode switch
  // event after the keyboard event.
  ui::DeviceEvent event_keyboard(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event11"));
  ui::DeviceEvent event_mode_switch(ui::DeviceEvent::DeviceType::INPUT,
                                    ui::DeviceEvent::ActionType::ADD,
                                    base::FilePath("/dev/input/event12"));
  provider_->OnDeviceEvent(event_keyboard);
  provider_->OnDeviceEvent(event_mode_switch);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
  EXPECT_EQ(mojom::TopRightKey::kLock, keyboard->top_right_key);
}

TEST_F(InputDataProviderTest, KeyboardTopRightKey_Detachable) {
  // "Internal" keyboards which are connected by USB are actually detachable,
  // and therefore should be assumed to have a lock key in the top-right.
  ui::DeviceEvent event_keyboard(ui::DeviceEvent::DeviceType::INPUT,
                                 ui::DeviceEvent::ActionType::ADD,
                                 base::FilePath("/dev/input/event13"));
  provider_->OnDeviceEvent(event_keyboard);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());

  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
  EXPECT_EQ(mojom::TopRightKey::kLock, keyboard->top_right_key);
}

TEST_F(InputDataProviderTest, ObserveConnectedDevices_Keyboards) {
  FakeConnectedDevicesObserver fake_observer;
  provider_->ObserveConnectedDevices(
      fake_observer.receiver.BindNewPipeAndPassRemote());

  ui::DeviceEvent add_keyboard_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::ADD,
                                     base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(add_keyboard_event);
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(1ul, fake_observer.keyboards_connected.size());
  EXPECT_EQ(4u, fake_observer.keyboards_connected[0]->id);

  ui::DeviceEvent remove_keyboard_event(ui::DeviceEvent::DeviceType::INPUT,
                                        ui::DeviceEvent::ActionType::REMOVE,
                                        base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(remove_keyboard_event);
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(1ul, fake_observer.keyboards_disconnected.size());
  EXPECT_EQ(4u, fake_observer.keyboards_disconnected[0]);
}

TEST_F(InputDataProviderTest, ObserveConnectedDevices_TouchDevices) {
  FakeConnectedDevicesObserver fake_observer;
  provider_->ObserveConnectedDevices(
      fake_observer.receiver.BindNewPipeAndPassRemote());

  ui::DeviceEvent add_touch_event(ui::DeviceEvent::DeviceType::INPUT,
                                  ui::DeviceEvent::ActionType::ADD,
                                  base::FilePath("/dev/input/event1"));
  provider_->OnDeviceEvent(add_touch_event);
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(1ul, fake_observer.touch_devices_connected.size());
  EXPECT_EQ(1u, fake_observer.touch_devices_connected[0]->id);

  ui::DeviceEvent remove_touch_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::REMOVE,
                                     base::FilePath("/dev/input/event1"));
  provider_->OnDeviceEvent(remove_touch_event);
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(1ul, fake_observer.touch_devices_disconnected.size());
  EXPECT_EQ(1u, fake_observer.touch_devices_disconnected[0]);
}

TEST_F(InputDataProviderTest, ObserveConnectedDevices_NoExternalKeyboards) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(
      features::kEnableExternalKeyboardsInDiagnostics);

  FakeConnectedDevicesObserver fake_observer;
  provider_->ObserveConnectedDevices(
      fake_observer.receiver.BindNewPipeAndPassRemote());

  ui::DeviceEvent add_external_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::ADD,
                                     base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(add_external_event);
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(0ul, fake_observer.keyboards_connected.size());

  ui::DeviceEvent remove_external_event(ui::DeviceEvent::DeviceType::INPUT,
                                        ui::DeviceEvent::ActionType::REMOVE,
                                        base::FilePath("/dev/input/event4"));
  provider_->OnDeviceEvent(remove_external_event);
  base::RunLoop().RunUntilIdle();
  ASSERT_EQ(0ul, fake_observer.keyboards_disconnected.size());
}

TEST_F(InputDataProviderTest, ChangeDeviceDoesNotCrash) {
  ui::DeviceEvent add_device_event(ui::DeviceEvent::DeviceType::INPUT,
                                   ui::DeviceEvent::ActionType::ADD,
                                   base::FilePath("/dev/input/event1"));
  ui::DeviceEvent change_device_event(ui::DeviceEvent::DeviceType::INPUT,
                                      ui::DeviceEvent::ActionType::CHANGE,
                                      base::FilePath("/dev/input/event1"));
  provider_->OnDeviceEvent(add_device_event);
  base::RunLoop().RunUntilIdle();
  provider_->OnDeviceEvent(change_device_event);
  base::RunLoop().RunUntilIdle();
}

TEST_F(InputDataProviderTest, BadDeviceDoesNotCrash) {
  // Try a device that specifically fails to be processed.
  ui::DeviceEvent add_bad_device_event(ui::DeviceEvent::DeviceType::INPUT,
                                       ui::DeviceEvent::ActionType::ADD,
                                       base::FilePath("/dev/input/event99"));
  provider_->OnDeviceEvent(add_bad_device_event);
  base::RunLoop().RunUntilIdle();
}

TEST_F(InputDataProviderTest, SillyDeviceDoesNotCrash) {
  // Try a device that has data, but has a non-parseable name.
  ui::DeviceEvent add_silly_device_event(ui::DeviceEvent::DeviceType::INPUT,
                                         ui::DeviceEvent::ActionType::ADD,
                                         base::FilePath(kSillyDeviceName));
  provider_->OnDeviceEvent(add_silly_device_event);
  base::RunLoop().RunUntilIdle();
}

TEST_F(InputDataProviderTest, GetKeyboardMechanicalLayout_Unknown1) {
  statistics_provider_.ClearMachineStatistic(
      system::kKeyboardMechanicalLayoutKey);

  ui::DeviceEvent add_keyboard_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::ADD,
                                     base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(add_keyboard_event);
  base::RunLoop().RunUntilIdle();

  {
    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                           std::vector<mojom::TouchDeviceInfoPtr>>
        future;
    provider_->GetConnectedDevices(future.GetCallback());

    const auto& keyboards = future.Get<0>();

    ASSERT_EQ(1ul, keyboards.size());

    const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
    EXPECT_EQ(6u, builtin_keyboard->id);
    EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
              builtin_keyboard->physical_layout);
    EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
              builtin_keyboard->mechanical_layout);
    EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
              builtin_keyboard->number_pad_present);
  }
}

TEST_F(InputDataProviderTest, GetKeyboardMechanicalLayout_Unknown2) {
  statistics_provider_.SetMachineStatistic(system::kKeyboardMechanicalLayoutKey,
                                           kInvalidMechnicalLayout);
  ui::DeviceEvent add_keyboard_event(ui::DeviceEvent::DeviceType::INPUT,
                                     ui::DeviceEvent::ActionType::ADD,
                                     base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(add_keyboard_event);
  base::RunLoop().RunUntilIdle();

  {
    base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                           std::vector<mojom::TouchDeviceInfoPtr>>
        future;
    provider_->GetConnectedDevices(future.GetCallback());

    const auto& keyboards = future.Get<0>();

    ASSERT_EQ(1ul, keyboards.size());

    const mojom::KeyboardInfoPtr& builtin_keyboard = keyboards[0];
    EXPECT_EQ(6u, builtin_keyboard->id);
    EXPECT_EQ(mojom::PhysicalLayout::kChromeOS,
              builtin_keyboard->physical_layout);
    EXPECT_EQ(mojom::MechanicalLayout::kUnknown,
              builtin_keyboard->mechanical_layout);
    EXPECT_EQ(mojom::NumberPadPresence::kNotPresent,
              builtin_keyboard->number_pad_present);
  }
}

TEST_F(InputDataProviderTest, ResetReceiverOnBindInterface) {
  // This test simulates a user refreshing the WebUI page. The receiver should
  // be reset before binding the new receiver. Otherwise we would get a DCHECK
  // error from mojo::Receiver
  mojo::Remote<mojom::InputDataProvider> remote;
  provider_->BindInterface(remote.BindNewPipeAndPassReceiver());
  base::RunLoop().RunUntilIdle();

  remote.reset();

  provider_->BindInterface(remote.BindNewPipeAndPassReceiver());
  base::RunLoop().RunUntilIdle();
}

TEST_F(InputDataProviderTest, KeyObservationBasic) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer->events_.size());
  EXPECT_EQ(0u, provider_->watchers_->size());

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  // Ensure an event watcher was constructed for the observer,
  // but has not posted any events.
  EXPECT_EQ(0u, fake_observer->events_.size());
  EXPECT_EQ(1u, provider_->watchers_->size());
  ASSERT_TRUE((*provider_->watchers_)[6]);

  // Post a key event through the watcher that
  // was created for the observer.
  (*provider_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                           kKeyA.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Ensure the event came through.
  EXPECT_EQ(1u, fake_observer->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kEvent, fake_observer->events_[0].first);
  ASSERT_TRUE(fake_observer->events_[0].second);

  EXPECT_EQ(*fake_observer->events_[0].second,
            mojom::KeyEvent(/*id=*/6u, /*type=*/mojom::KeyEventType::kPress,
                            /*key_code=*/kKeyA.key_code,
                            /*scan_code=*/kKeyA.at_scan_code,
                            /*top_row_position=*/-1));
}

TEST_F(InputDataProviderTest, KeyObservationRemoval) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer->events_.size());
  EXPECT_EQ(0u, provider_->watchers_->size());

  bool disconnected = false;

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  fake_observer->receiver.set_disconnect_handler(
      base::BindOnce([](bool* disconnected) { *disconnected = true; },
                     base::Unretained(&disconnected)));

  base::RunLoop().RunUntilIdle();

  // Ensure an event watcher was constructed for the observer,
  // but has not posted any events.
  EXPECT_EQ(0u, fake_observer->events_.size());
  EXPECT_EQ(1u, provider_->watchers_->size());
  EXPECT_FALSE(disconnected);
  ASSERT_TRUE((*provider_->watchers_)[6]);

  // Test a key event.
  EXPECT_KEY_EVENTS(fake_observer.get(), 6u, {{kKeyA, -1}});

  // Disconnect keyboard while it is being observed.
  ui::DeviceEvent remove_kbd_event(ui::DeviceEvent::DeviceType::INPUT,
                                   ui::DeviceEvent::ActionType::REMOVE,
                                   base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(remove_kbd_event);
  base::RunLoop().RunUntilIdle();

  // Watcher should have been shut down, and receiver disconnected.
  EXPECT_FALSE((*provider_->watchers_)[6]);
  EXPECT_TRUE(disconnected);
}

TEST_F(InputDataProviderTest, KeyObservationMultiple) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE((*provider_->watchers_)[6]);

  EXPECT_KEY_EVENTS(fake_observer.get(), 6u,
                    {{kKeyA, -1, true},
                     {kKeyB, -1, true},
                     {kKeyA, -1, false},
                     {kKeyB, -1, false}});
}

TEST_F(InputDataProviderTest, KeyObservationObeysFocus) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  provider_->attached_widget_->Deactivate();
  provider_->attached_widget_->Hide();

  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  // Verify we got the pause event from hiding the window.
  ASSERT_EQ(1u, fake_observer->events_.size());
  ASSERT_TRUE((*provider_->watchers_)[6]);

  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer->events_[0].first);

  // Post a key event through the watcher that
  // was created for the observer.
  (*provider_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                           kKeyA.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Ensure the event did not come through, as the widget was not visible and
  // focused.
  ASSERT_EQ(1u, fake_observer->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer->events_[0].first);
}

TEST_F(InputDataProviderTest, KeyObservationDisconnect) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer->events_.size());
  ASSERT_TRUE((*provider_->watchers_)[6]);

  fake_observer->receiver.reset();
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer->events_.size());
  ASSERT_FALSE((*provider_->watchers_)[6]);
}

TEST_F(InputDataProviderTest, KeyObservationObeysFocusSwitching) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();
  std::unique_ptr<views::Widget> other_widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);

  // Provider's widget must be active and visible.
  provider_->attached_widget_->Show();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer->events_.size());
  EXPECT_EQ(0u, provider_->watchers_->size());

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  // Ensure an event watcher was constructed for the observer,
  // but has not posted any events.
  EXPECT_EQ(0u, fake_observer->events_.size());
  EXPECT_EQ(1u, provider_->watchers_->size());
  ASSERT_TRUE((*provider_->watchers_)[6]);

  // Focus on the other window.
  other_widget->Show();
  other_widget->Activate();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(provider_->attached_widget_->IsActive());
  EXPECT_TRUE(other_widget->IsVisible());
  EXPECT_TRUE(other_widget->IsActive());

  EXPECT_EQ(1u, fake_observer->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer->events_[0].first);
  ASSERT_FALSE(fake_observer->events_[0].second);

  // Post a key event through the watcher that
  // was created for the observer.
  (*provider_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                           kKeyA.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Ensure the event did not come through.
  EXPECT_EQ(1u, fake_observer->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer->events_[0].first);

  // Clear events for next round.
  fake_observer->events_.clear();

  // Switch windows back.
  provider_->attached_widget_->Show();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(provider_->attached_widget_->IsActive());
  EXPECT_FALSE(other_widget->IsActive());

  // Post another key event.
  (*provider_->watchers_)[6]->PostKeyEvent(true, kKeyB.key_code,
                                           kKeyB.at_scan_code);
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(2u, fake_observer->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kResume, fake_observer->events_[0].first);
  EXPECT_EQ(FakeKeyboardObserver::kEvent, fake_observer->events_[1].first);
  ASSERT_TRUE(fake_observer->events_[1].second);

  EXPECT_EQ(*fake_observer->events_[1].second,
            mojom::KeyEvent(/*id=*/6u, /*type=*/mojom::KeyEventType::kPress,
                            /*key_code=*/kKeyB.key_code,
                            /*scan_code=*/kKeyB.at_scan_code,
                            /*top_row_position=*/-1));
}

TEST_F(InputDataProviderTest, ShortcutBlockingObeysFocus) {
  const std::string kDevicePath("/dev/input/event6");
  const uint32_t kDeviceId = 6u;

  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  provider_->attached_widget_->Deactivate();
  provider_->attached_widget_->Hide();

  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath(kDevicePath));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(OpenAndCloseLauncher());
  EXPECT_FALSE(ModifierRewritesAreSuppressed());
  EXPECT_TRUE(InputDataProvider::ShouldCloseDialogOnEscape());

  // If widget is in focus, ObserveKeyEvents should block shortcuts, however
  // since the widget is not in focus, it does not block
  provider_->ObserveKeyEvents(
      kDeviceId, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(OpenAndCloseLauncher());
  EXPECT_FALSE(ModifierRewritesAreSuppressed());
  EXPECT_TRUE(InputDataProvider::ShouldCloseDialogOnEscape());
}

TEST_F(InputDataProviderTest, ShortcutBlockingObeysFocusSwitching) {
  const std::string kDevicePath("/dev/input/event6");
  const uint32_t kDeviceId = 6u;

  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  provider_->attached_widget_->Show();

  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath(kDevicePath));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(OpenAndCloseLauncher());
  EXPECT_FALSE(ModifierRewritesAreSuppressed());
  EXPECT_TRUE(InputDataProvider::ShouldCloseDialogOnEscape());

  // If widget is in focus, ObserveKeyEvents should block shortcuts
  provider_->ObserveKeyEvents(
      kDeviceId, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(OpenAndCloseLauncher());
  EXPECT_TRUE(ModifierRewritesAreSuppressed());
  EXPECT_FALSE(InputDataProvider::ShouldCloseDialogOnEscape());

  // Hide widget and check that we can use shortcuts
  provider_->attached_widget_->Hide();
  EXPECT_TRUE(OpenAndCloseLauncher());
  EXPECT_FALSE(ModifierRewritesAreSuppressed());
  EXPECT_TRUE(InputDataProvider::ShouldCloseDialogOnEscape());

  // Show widget and check that shortcuts are blocked
  provider_->attached_widget_->Show();
  EXPECT_FALSE(OpenAndCloseLauncher());
  EXPECT_TRUE(ModifierRewritesAreSuppressed());
  EXPECT_FALSE(InputDataProvider::ShouldCloseDialogOnEscape());
}

TEST_F(InputDataProviderTest, ShortcutBlockingObeysLastObserverDisconnect) {
  const std::string kDevicePath("/dev/input/event6");
  const uint32_t kDeviceId = 6u;

  std::unique_ptr<FakeKeyboardObserver> fake_observer1 =
      std::make_unique<FakeKeyboardObserver>();
  std::unique_ptr<FakeKeyboardObserver> fake_observer2 =
      std::make_unique<FakeKeyboardObserver>();

  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Shortcuts are still available after device is added
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath(kDevicePath));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(OpenAndCloseLauncher());
  EXPECT_FALSE(ModifierRewritesAreSuppressed());
  EXPECT_TRUE(InputDataProvider::ShouldCloseDialogOnEscape());

  // If widget is in focus, ObserveKeyEvents should block shortcuts
  provider_->ObserveKeyEvents(
      kDeviceId, fake_observer1->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(OpenAndCloseLauncher());
  EXPECT_TRUE(ModifierRewritesAreSuppressed());
  EXPECT_FALSE(InputDataProvider::ShouldCloseDialogOnEscape());

  // When second observer is added, shortcuts should still be blocked
  provider_->ObserveKeyEvents(
      kDeviceId, fake_observer2->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(OpenAndCloseLauncher());
  EXPECT_TRUE(ModifierRewritesAreSuppressed());
  EXPECT_FALSE(InputDataProvider::ShouldCloseDialogOnEscape());

  // When first observer is destroyed, shortcuts should still be blocked
  fake_observer1.reset();
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(OpenAndCloseLauncher());
  EXPECT_TRUE(ModifierRewritesAreSuppressed());
  EXPECT_FALSE(InputDataProvider::ShouldCloseDialogOnEscape());

  // After second observer is destroyed, shortcuts should be unblocked
  fake_observer2.reset();
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(OpenAndCloseLauncher());
  EXPECT_FALSE(ModifierRewritesAreSuppressed());
  EXPECT_TRUE(InputDataProvider::ShouldCloseDialogOnEscape());
}

// Test overlapping lifetimes of separate observers of one device.
TEST_F(InputDataProviderTest, KeyObservationOverlappingeObserversOfDevice) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer1 =
      std::make_unique<FakeKeyboardObserver>();

  provider_->attached_widget_->Show();

  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  provider_->ObserveKeyEvents(
      6u, fake_observer1->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer1->events_.size());
  EXPECT_EQ(1u, provider_->watchers_->size());
  EXPECT_TRUE((*provider_->watchers_)[6]);

  std::unique_ptr<FakeKeyboardObserver> fake_observer2 =
      std::make_unique<FakeKeyboardObserver>();

  provider_->ObserveKeyEvents(
      6u, fake_observer2->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer1->events_.size());
  EXPECT_EQ(0u, fake_observer2->events_.size());
  EXPECT_TRUE((*provider_->watchers_)[6]);

  fake_observer1.reset();
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, fake_observer2->events_.size());
  ASSERT_TRUE((*provider_->watchers_)[6]);

  // And send an event through to check functionality.
  (*provider_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                           kKeyA.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Ensure an event comes through properly after all that.
  EXPECT_EQ(1u, fake_observer2->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kEvent, fake_observer2->events_[0].first);
  ASSERT_TRUE(fake_observer2->events_[0].second);
  EXPECT_EQ(*fake_observer2->events_[0].second,
            mojom::KeyEvent(/*id=*/6u, /*type=*/mojom::KeyEventType::kPress,
                            /*key_code=*/kKeyA.key_code,
                            /*scan_code=*/kKeyA.at_scan_code,
                            /*top_row_position=*/-1));

  fake_observer2.reset();
  base::RunLoop().RunUntilIdle();

  EXPECT_EQ(0u, provider_->watchers_->count(6));
}

// Double-check security model and ensure that multiple instances
// do not interfere with each other, and that key observations obey
// individual window focus combined with multiple instances.
TEST_F(InputDataProviderTest, KeyObservationMultipleProviders) {
  // Create a second InputDataProvider, with a separate window/widget,
  // as would happen if multiple instances of the SWA were created.
  watchers_t provider2_watchers;
  auto provider2_widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);

  std::unique_ptr<TestInputDataProvider> provider2_ =
      std::make_unique<TestInputDataProvider>(provider2_widget.get(),
                                              provider2_watchers,
                                              &event_rewriter_delegate_);
  auto& provider1_ = provider_;

  std::unique_ptr<FakeKeyboardObserver> fake_observer1 =
      std::make_unique<FakeKeyboardObserver>();
  std::unique_ptr<FakeKeyboardObserver> fake_observer2 =
      std::make_unique<FakeKeyboardObserver>();

  // Show and activate first window.
  provider1_->attached_widget_->Show();
  // Show and activate second window; this will deactivate the first window.
  provider2_->attached_widget_->Show();

  EXPECT_FALSE(provider1_->attached_widget_->IsActive());
  EXPECT_TRUE(provider2_->attached_widget_->IsActive());

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider1_->OnDeviceEvent(event0);
  provider2_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(provider1_->watchers_->empty());
  EXPECT_TRUE(provider2_->watchers_->empty());

  // Connected observer 1 to provider 1.
  provider1_->ObserveKeyEvents(
      6u, fake_observer1->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(provider1_->watchers_->empty());
  EXPECT_TRUE(provider2_->watchers_->empty());
  EXPECT_EQ(1u, fake_observer1->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer1->events_[0].first);
  EXPECT_EQ(1u, provider1_->watchers_->count(6));

  EXPECT_EQ(0u, fake_observer2->events_.size());
  EXPECT_EQ(0u, provider2_->watchers_->count(6));

  // Connected observer 2 to provider 2.

  provider2_->ObserveKeyEvents(
      6u, fake_observer2->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(provider1_->watchers_->empty());
  EXPECT_FALSE(provider2_->watchers_->empty());
  EXPECT_EQ(1u, fake_observer1->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer1->events_[0].first);
  EXPECT_EQ(1u, provider1_->watchers_->size());
  EXPECT_EQ(1u, provider1_->watchers_->size());
  ASSERT_TRUE((*provider1_->watchers_)[6]);
  ASSERT_TRUE((*provider2_->watchers_)[6]);

  EXPECT_EQ(0u, fake_observer2->events_.size());
  EXPECT_EQ(1u, provider2_->watchers_->size());
  EXPECT_TRUE((*provider2_->watchers_)[6]);
  // Providers should have distinct Watcher instances.
  EXPECT_NE((*provider1_->watchers_)[6], (*provider2_->watchers_)[6]);

  // Reset event logs for next round.
  fake_observer1->events_.clear();
  fake_observer2->events_.clear();

  // Post two separate key events.
  (*provider1_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                            kKeyA.at_scan_code);
  (*provider2_->watchers_)[6]->PostKeyEvent(true, kKeyB.key_code,
                                            kKeyB.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Ensure the events came through to expected targets.
  EXPECT_EQ(0u, fake_observer1->events_.size());

  EXPECT_EQ(1u, fake_observer2->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kEvent, fake_observer2->events_[0].first);
  ASSERT_TRUE(fake_observer2->events_[0].second);
  EXPECT_EQ(*fake_observer2->events_[0].second,
            mojom::KeyEvent(/*id=*/6u, /*type=*/mojom::KeyEventType::kPress,
                            /*key_code=*/kKeyB.key_code,
                            /*scan_code=*/kKeyB.at_scan_code,
                            /*top_row_position=*/-1));

  // Reset event logs for next round.
  fake_observer1->events_.clear();
  fake_observer2->events_.clear();

  // Switch active window.
  provider1_->attached_widget_->Activate();
  base::RunLoop().RunUntilIdle();

  EXPECT_TRUE(provider1_->attached_widget_->IsActive());
  EXPECT_TRUE(provider1_->attached_widget_->IsVisible());
  EXPECT_FALSE(provider2_->attached_widget_->IsActive());

  (*provider1_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                            kKeyA.at_scan_code);
  (*provider2_->watchers_)[6]->PostKeyEvent(true, kKeyB.key_code,
                                            kKeyB.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Ensure the events came through to expected targets.

  EXPECT_EQ(2u, fake_observer1->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kResume, fake_observer1->events_[0].first);
  EXPECT_FALSE(fake_observer1->events_[0].second);
  EXPECT_EQ(FakeKeyboardObserver::kEvent, fake_observer1->events_[1].first);
  ASSERT_TRUE(fake_observer1->events_[1].second);
  EXPECT_EQ(*fake_observer1->events_[1].second,
            mojom::KeyEvent(/*id=*/6u, /*type=*/mojom::KeyEventType::kPress,
                            /*key_code=*/kKeyA.key_code,
                            /*scan_code=*/kKeyA.at_scan_code,
                            /*top_row_position=*/-1));

  EXPECT_EQ(1u, fake_observer2->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer2->events_[0].first);
  EXPECT_FALSE(fake_observer2->events_[0].second);

  // Reset event logs for next round.
  fake_observer1->events_.clear();
  fake_observer2->events_.clear();

  // Activate a new widget, ensuring neither previous window is active.
  auto widget3 =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
  widget3->Activate();
  base::RunLoop().RunUntilIdle();

  EXPECT_FALSE(provider1_->attached_widget_->IsActive());
  EXPECT_FALSE(provider2_->attached_widget_->IsActive());

  // Event should show key paused from previously active window.
  EXPECT_EQ(1u, fake_observer1->events_.size());
  EXPECT_EQ(FakeKeyboardObserver::kPause, fake_observer1->events_[0].first);
  EXPECT_FALSE(fake_observer1->events_[0].second);

  // Reset event logs for next round.
  fake_observer1->events_.clear();
  fake_observer2->events_.clear();

  // Deliver keys to both.
  (*provider1_->watchers_)[6]->PostKeyEvent(true, kKeyA.key_code,
                                            kKeyA.at_scan_code);
  (*provider2_->watchers_)[6]->PostKeyEvent(true, kKeyB.key_code,
                                            kKeyB.at_scan_code);
  base::RunLoop().RunUntilIdle();

  // Neither window is visible and active, no events should be received.
  EXPECT_FALSE(provider1_->attached_widget_->IsVisible() &&
               provider1_->attached_widget_->IsActive());
  EXPECT_FALSE(provider2_->attached_widget_->IsVisible() &&
               provider2_->attached_widget_->IsActive());
  EXPECT_EQ(0u, fake_observer1->events_.size());
  EXPECT_EQ(0u, fake_observer2->events_.size());
}

TEST_F(InputDataProviderTest, KeyObservationTopRowBasic) {
  // Test with Eve keyboard: [Escape, Back, ..., Louder, Menu]
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE((*provider_->watchers_)[6]);

  EXPECT_KEY_EVENTS(fake_observer.get(), 6u,
                    {{kKeyEsc, -1},
                     {kKeyF1, 0},
                     {kKeyF10, 9},
                     {kKeyMenu, -1},
                     {kKeyDelete, -1}});
}

TEST_F(InputDataProviderTest, KeyObservationTopRowUnknownAction) {
  // Test for Vivaldi descriptor having an unrecognized scan-code;
  // most likely due to external keyboard being newer than OS image.

  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  std::vector<mojom::TopRowKey> modified_top_row_keys =
      std::vector(std::begin(kInternalJinlonTopRowKeys),
                  std::end(kInternalJinlonTopRowKeys));
  modified_top_row_keys[kUnknownScancodeIndex] = mojom::TopRowKey::kUnknown;

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event11"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      future;
  provider_->GetConnectedDevices(future.GetCallback());

  const auto& keyboards = future.Get<0>();

  ASSERT_EQ(1ul, keyboards.size());
  const mojom::KeyboardInfoPtr& keyboard = keyboards[0];
  EXPECT_EQ(11u, keyboard->id);
  EXPECT_EQ(modified_top_row_keys, keyboard->top_row_keys);

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      11u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE((*provider_->watchers_)[11]);

  EXPECT_KEY_EVENTS(fake_observer.get(), 11u,
                    {{kKeyEsc, -1},
                     {kKeyActionBack, 0},
                     {kKeyF1, 0},
                     {kKeyActionRefresh, 1},
                     {kKeyActionFullscreen, 2},
                     {kKeyActionOverview, 3},
                     {kKeyActionScreenshot, 4},
                     {kKeyActionScreenBrightnessDown, 5},
                     {kKeyActionScreenBrightnessUp, 6},
                     {{0, kUnknownScancode, 0}, kUnknownScancodeIndex},
                     {kKeyF8, 7},
                     {kKeyActionKeyboardBrightnessDown, 8},
                     {kKeyActionKeyboardBrightnessUp, 9},
                     {kKeyActionKeyboardVolumeMute, 10},
                     {kKeyF10, 9},
                     {kKeyActionKeyboardVolumeDown, 11},
                     {kKeyActionKeyboardVolumeUp, 12},
                     {kKeySleep, -1}});
}

// TODO(b/208729519): Not available until we can test Drallion keyboards.
#if 0
TEST_F(InputDataProviderTest, KeyObservationTopRowDrallion) {
  // Test with Drallion keyboard:
  //  [Escape, Back, ..., Louder, F10, F11, F12, Mirror, Delete]
  // ...

  // Construct a keyboard
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event10"));
  // ...
  struct {
    KeyDefinition key;
    int position;
  } keys[] = {
      {kKeyA, -1},
      {kKeyB, -1},
      {kKeyEsc, -1},
      {kKeyF1, 0},
      {kKeyF10, 9},
      {kKeyF11, 10},
      {kKeyF12, 11},
      {kKeySwitchVideoMode, 12},
      {kKeyDelete, 13},
  };

  for (size_t i = 0; i < std::size(keys); i++) {
    auto item = keys[i];
    provider_->watchers_[10]->PostKeyEvent(true, item.key.key_code,
         item.key.at_scan_code);
  }
  base::RunLoop().RunUntilIdle();

  // ...
}
#endif  // 0

TEST_F(InputDataProviderTest, KeyObservationTopRowExternalUSB) {
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event9"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      9u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE((*provider_->watchers_)[9]);

  // Test with generic external keyboard.
  EXPECT_KEY_EVENTS(fake_observer.get(), 9u,
                    {{kKeyA, -1},
                     {kKeyB, -1},
                     {kKeyMenu, -1},
                     {kKeyDelete, -1},
                     {kKeyEsc, -1},
                     {kKeyF1, 0},
                     {kKeyF10, 9},
                     {kKeyF11, 10},
                     {kKeyF12, 11}});
}

TEST_F(InputDataProviderTest, KeyboardInputLog) {
  base::ScopedTempDir temp_dir;
  base::FilePath log_path;
  EXPECT_TRUE(temp_dir.CreateUniqueTempDir());
  log_path = temp_dir.GetPath();
  const auto full_log_path = log_path.AppendASCII("keyboard_input.log");
  DiagnosticsLogController::Get()->SetKeyboardInputLogForTesting(
      std::make_unique<KeyboardInputLog>(log_path));
  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE((*provider_->watchers_)[6]);

  // Test a key event.
  EXPECT_KEY_EVENTS(fake_observer.get(), 6u, {{kKeyA, -1}});

  // Disconnect keyboard while it is being observed.
  ui::DeviceEvent remove_kbd_event(ui::DeviceEvent::DeviceType::INPUT,
                                   ui::DeviceEvent::ActionType::REMOVE,
                                   base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(remove_kbd_event);
  base::RunLoop().RunUntilIdle();

  // Watcher should have been shut down, and receiver disconnected.
  EXPECT_FALSE((*provider_->watchers_)[6]);
  task_environment()->RunUntilIdle();
  std::string contents;
  EXPECT_TRUE(base::ReadFileToString(full_log_path, &contents));

  std::vector<std::string> lines = GetLogLines(contents);
  ASSERT_EQ(2u, lines.size());

  // First line is of form:
  // [TimeStamp] - Key press test - AT Translated Set 2 keyboard
  std::vector<std::string> first_line_contents =
      GetLogLineContents(lines[0], "-");
  ASSERT_EQ(3u, first_line_contents.size());
  EXPECT_EQ("Key press test", first_line_contents[1]);
  EXPECT_EQ("AT Translated Set 2 keyboard", first_line_contents[2]);

  EXPECT_EQ("Key code: 30, Scan code: 30", lines[1]);
}

TEST_F(InputDataProviderTest, KeyboardTesterRoutineDurationMetric) {
  base::HistogramTester histogram_tester;

  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      /*id=*/6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  task_environment()->FastForwardBy(
      base::Seconds(kKeyboardTesterMetricTimeDelay));
  ASSERT_TRUE((*provider_->watchers_)[6]);

  // Test a key event.
  EXPECT_KEY_EVENTS(fake_observer.get(), /*id=*/6u, {{kKeyA, -1}});

  // Disconnect keyboard while it is being observed.
  ui::DeviceEvent remove_kbd_event(ui::DeviceEvent::DeviceType::INPUT,
                                   ui::DeviceEvent::ActionType::REMOVE,
                                   base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(remove_kbd_event);
  base::RunLoop().RunUntilIdle();

  // Watcher should have been shut down, and receiver disconnected.
  EXPECT_FALSE((*provider_->watchers_)[6]);
  task_environment()->RunUntilIdle();

  histogram_tester.ExpectUniqueTimeSample(
      "ChromeOS.DiagnosticsUi.KeyboardTesterRoutineDuration",
      base::Seconds(kKeyboardTesterMetricTimeDelay),
      /*expected_bucket_count=*/1);
}

TEST_F(InputDataProviderTest,
       KeyboardTesterRoutineDurationMetricOnDestruction) {
  base::HistogramTester histogram_tester;

  std::unique_ptr<FakeKeyboardObserver> fake_observer =
      std::make_unique<FakeKeyboardObserver>();

  // Widget must be active and visible.
  provider_->attached_widget_->Show();
  provider_->attached_widget_->Activate();

  // Construct a keyboard.
  const ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                               ui::DeviceEvent::ActionType::ADD,
                               base::FilePath("/dev/input/event6"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  // Attach a key observer.
  provider_->ObserveKeyEvents(
      /*id=*/6u, fake_observer->receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  task_environment()->FastForwardBy(
      base::Seconds(kKeyboardTesterMetricTimeDelay));
  ASSERT_TRUE((*provider_->watchers_)[6]);

  // Test a key event.
  EXPECT_KEY_EVENTS(fake_observer.get(), /*id=*/6u, {{kKeyA, -1}});

  // Manually destroy the provider.
  provider_.reset();

  histogram_tester.ExpectUniqueTimeSample(
      "ChromeOS.DiagnosticsUi.KeyboardTesterRoutineDuration",
      base::Seconds(kKeyboardTesterMetricTimeDelay),
      /*expected_bucket_count=*/1);
}

// TODO(b/211780758): Test all Fx scancodes using
// ui/events/keycodes/dom/dom_code_data.inc as source of truth.

// Test the behavior when the tablet mode status has changed. The tablet mode
// is initialized as "not-in-tablet-mode".
TEST_F(InputDataProviderTest, TabletModeObservation) {
  FakeTabletModeObserver fake_observer;
  base::test::TestFuture<bool> future;

  // Attach a tablet mode observer.
  provider_->ObserveTabletMode(
      fake_observer.receiver.BindNewPipeAndPassRemote(), future.GetCallback());

  // Default initial state is "not-in-tablet-mode".
  ASSERT_FALSE(future.Get<0>());

  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(fake_observer.is_tablet_mode());
  EXPECT_EQ(1u, fake_observer.num_tablet_mode_change_calls());

  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(fake_observer.is_tablet_mode());
  EXPECT_EQ(2u, fake_observer.num_tablet_mode_change_calls());
}

// Test the behavior when the tablet mode status has changed. The tablet mode
// is initialized as "in-tablet-mode".
TEST_F(InputDataProviderTest, TabletModeObservationInitAsTabletMode) {
  FakeTabletModeObserver fake_observer;
  base::test::TestFuture<bool> future;

  // Set initial state as tablet mode.
  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);

  // Attach a tablet mode observer.
  provider_->ObserveTabletMode(
      fake_observer.receiver.BindNewPipeAndPassRemote(), future.GetCallback());

  // Initial state is set to "in-tablet-mode".
  ASSERT_TRUE(future.Get<0>());
}

// Test the behavior when the lid state has changed. The lid state is
// initialized as open.
TEST_F(InputDataProviderTest, LidStateObservation) {
  // Need to run loop here to give constructor time to receive response from
  // FakePowerManagerClient.
  base::RunLoop().RunUntilIdle();

  FakeLidStateObserver fake_observer;
  base::test::TestFuture<bool> future;

  power_manager_client()->SetLidState(
      chromeos::PowerManagerClient::LidState::OPEN, /*timestamp=*/{});

  // Attach a lid state observer.
  provider_->ObserveLidState(fake_observer.receiver.BindNewPipeAndPassRemote(),
                             future.GetCallback());
  base::RunLoop().RunUntilIdle();

  // Default state is that lid is open.
  ASSERT_TRUE(future.Get<0>());

  power_manager_client()->SetLidState(
      chromeos::PowerManagerClient::LidState::CLOSED, /*timestamp=*/{});
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(fake_observer.is_lid_open());
  EXPECT_EQ(1u, fake_observer.num_lid_state_change_calls());

  power_manager_client()->SetLidState(
      chromeos::PowerManagerClient::LidState::OPEN, /*timestamp=*/{});
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(fake_observer.is_lid_open());
  EXPECT_EQ(2u, fake_observer.num_lid_state_change_calls());
}

// Test the behavior when the lid state status has changed. The lid state is
// initialized as closed.
TEST_F(InputDataProviderTest, LidStateObservationInitAsClosed) {
  // Need to run loop here to give constructor time to receive response from
  // FakePowerManagerClient.
  base::RunLoop().RunUntilIdle();

  FakeLidStateObserver fake_observer;
  base::test::TestFuture<bool> future;

  power_manager_client()->SetLidState(
      chromeos::PowerManagerClient::LidState::CLOSED, /*timestamp=*/{});

  // Attach a tablet mode observer.
  provider_->ObserveLidState(fake_observer.receiver.BindNewPipeAndPassRemote(),
                             future.GetCallback());
  base::RunLoop().RunUntilIdle();

  // Default state is that lid is closed.
  ASSERT_FALSE(future.Get<0>());
}

// Test the behavior when the lid state status has changed. The lid state is
// initialized as closed.
TEST_F(InputDataProviderTest, LidStateObservationInitAsUnsupported) {
  // Need to run loop here to give constructor time to receive response from
  // FakePowerManagerClient.
  base::RunLoop().RunUntilIdle();

  FakeLidStateObserver fake_observer;
  base::test::TestFuture<bool> future;

  power_manager_client()->SetLidState(
      chromeos::PowerManagerClient::LidState::NOT_PRESENT, /*timestamp=*/{});

  // Attach a tablet mode observer.
  provider_->ObserveLidState(fake_observer.receiver.BindNewPipeAndPassRemote(),
                             future.GetCallback());
  base::RunLoop().RunUntilIdle();

  // Default state is that lid is unsupported, which should act as though lid is
  // open.
  ASSERT_TRUE(future.Get<0>());
}

// Test the behavior when the initial internal display power state is on.
TEST_F(InputDataProviderTest, InternalDisplayPowerStateAsDefault) {
  FakeInternalDisplayPowerStateObserver fake_observer;

  // Attach a internal display power state observer.
  provider_->ObserveInternalDisplayPowerState(
      fake_observer.receiver.BindNewPipeAndPassRemote());

  ASSERT_TRUE(provider_->is_internal_display_on());
}

// Test the behavior when the initial internal display power state is off.
TEST_F(InputDataProviderTest, InternalDisplayPowerStateAsOff) {
  FakeInternalDisplayPowerStateObserver fake_observer;

  // Set initial display state as internal off and external on.
  auto* displayConfigurator = Shell::Get()->display_configurator();
  displayConfigurator->reset_requested_power_state_for_test();
  displayConfigurator->SetInitialDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON);

  // Attach a internal display power state observer.
  provider_->ObserveInternalDisplayPowerState(
      fake_observer.receiver.BindNewPipeAndPassRemote());

  ASSERT_FALSE(provider_->is_internal_display_on());
}

// Test the behavior when the internal display power state has changed.
TEST_F(InputDataProviderTest, InternalDisplayPowerStateObserver) {
  FakeInternalDisplayPowerStateObserver fake_observer;

  // Attach a internal display power state observer.
  provider_->ObserveInternalDisplayPowerState(
      fake_observer.receiver.BindNewPipeAndPassRemote());

  provider_->OnPowerStateChanged(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON);
  base::RunLoop().RunUntilIdle();

  ASSERT_FALSE(fake_observer.is_display_on());
  EXPECT_EQ(1u, fake_observer.num_display_state_change_calls());

  provider_->OnPowerStateChanged(
      chromeos::DISPLAY_POWER_INTERNAL_ON_EXTERNAL_OFF);
  base::RunLoop().RunUntilIdle();

  ASSERT_TRUE(fake_observer.is_display_on());
  EXPECT_EQ(2u, fake_observer.num_display_state_change_calls());
}

// Test the behavior if the app is not already in the testing touchscreen, the
// app should be moved to the targeting touchscreen.
TEST_F(InputDataProviderTest, MoveAppToTestingScreen) {
  // Construct two touchscreens. "/dev/input/event2" and "/dev/input/event14"
  // map to touchscreens.
  ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event2"));
  ui::DeviceEvent event1(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event14"));
  provider_->OnDeviceEvent(event0);
  provider_->OnDeviceEvent(event1);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      getConnectedDevicesFuture;
  provider_->GetConnectedDevices(getConnectedDevicesFuture.GetCallback());

  const auto& keyboards = getConnectedDevicesFuture.Get<0>();
  const auto& touch_devices = getConnectedDevicesFuture.Get<1>();

  ASSERT_EQ(0ul, keyboards.size());
  ASSERT_EQ(2ul, touch_devices.size());

  // Set up three fake displays.
  UpdateDisplay("500x400, 600x400, 800x600");
  display::Screen* screen = display::Screen::GetScreen();
  const int64_t primary_display_id = screen->GetAllDisplays()[0].id();
  const int64_t secondary_display_id = screen->GetAllDisplays()[1].id();
  const int64_t third_display_id = screen->GetAllDisplays()[2].id();

  // Initialize two touchscreens in DeviceDataManager.
  std::vector<ui::TouchscreenDevice> touchscreen_devices;
  touchscreen_devices.push_back(ui::TouchscreenDevice(
      kDeviceId1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
      touch_devices[0]->name, /*size=*/gfx::Size(600, 400),
      /*touch_points=*/0));
  touchscreen_devices.push_back(ui::TouchscreenDevice(
      kDeviceId2, ui::InputDeviceType::INPUT_DEVICE_USB, touch_devices[1]->name,
      /*size=*/gfx::Size(800, 600),
      /*touch_points=*/0));
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(touchscreen_devices);

  // Associate the touchscreens in DeviceDataManager to the fake displays.
  std::vector<ui::TouchDeviceTransform> touch_device_transforms(2);
  touch_device_transforms[0].display_id = secondary_display_id;
  touch_device_transforms[0].device_id = kDeviceId1;
  touch_device_transforms[1].display_id = third_display_id;
  touch_device_transforms[1].device_id = kDeviceId2;
  ui::DeviceDataManager::GetInstance()->ConfigureTouchDevices(
      touch_device_transforms);

  // Before move: make sure the app is currently in the primary display.
  aura::Window* window = widget_->GetNativeWindow();
  ASSERT_EQ(primary_display_id, screen->GetDisplayNearestWindow(window).id());

  // Call MoveAppToTestingScreen function with the touchscreen evdev id 2,
  // which maps to the secondary display.
  provider_->MoveAppToTestingScreen(/*evdev_id=*/2);

  // Confirm the app has been moved to the secondary display.
  ASSERT_EQ(secondary_display_id, screen->GetDisplayNearestWindow(window).id());

  // Call MoveAppToTestingScreen function with the touchscreen evdev id 14,
  // which maps to the third display.
  provider_->MoveAppToTestingScreen(/*evdev_id=*/14);

  // Confirm the app has been moved to the third display.
  ASSERT_EQ(third_display_id, screen->GetDisplayNearestWindow(window).id());
}

// Test the app can be moved back to original screen.
TEST_F(InputDataProviderTest, MoveAppBackToPreviousScreen) {
  // Construct a touchscreen. "/dev/input/event2" maps to a touchscreen.
  ui::DeviceEvent event0(ui::DeviceEvent::DeviceType::INPUT,
                         ui::DeviceEvent::ActionType::ADD,
                         base::FilePath("/dev/input/event2"));
  provider_->OnDeviceEvent(event0);
  base::RunLoop().RunUntilIdle();

  base::test::TestFuture<std::vector<mojom::KeyboardInfoPtr>,
                         std::vector<mojom::TouchDeviceInfoPtr>>
      getConnectedDevicesFuture;
  provider_->GetConnectedDevices(getConnectedDevicesFuture.GetCallback());

  const auto& keyboards = getConnectedDevicesFuture.Get<0>();
  const auto& touch_devices = getConnectedDevicesFuture.Get<1>();

  ASSERT_EQ(0ul, keyboards.size());
  ASSERT_EQ(1ul, touch_devices.size());

  // Set up two fake displays.
  UpdateDisplay("500x400, 600x400");
  display::Screen* screen = display::Screen::GetScreen();
  const int64_t primary_display_id = screen->GetAllDisplays()[0].id();
  const int64_t secondary_display_id = screen->GetAllDisplays()[1].id();

  // Initialize one touchscreen in DeviceDataManager.
  std::vector<ui::TouchscreenDevice> touchscreen_devices;
  touchscreen_devices.push_back(ui::TouchscreenDevice(
      kDeviceId1, ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
      touch_devices[0]->name, /*size=*/gfx::Size(600, 400),
      /*touch_points=*/0));
  ui::DeviceDataManagerTestApi().SetTouchscreenDevices(touchscreen_devices);

  // Associate the touchscreen in DeviceDataManager to the fake display.
  std::vector<ui::TouchDeviceTransform> touch_device_transforms(1);
  touch_device_transforms[0].display_id = secondary_display_id;
  touch_device_transforms[0].device_id = kDeviceId1;
  ui::DeviceDataManager::GetInstance()->ConfigureTouchDevices(
      touch_device_transforms);

  // Before move: make sure the app is currently in the primary display.
  aura::Window* window = widget_->GetNativeWindow();
  ASSERT_EQ(primary_display_id, screen->GetDisplayNearestWindow(window).id());

  // Call MoveAppToTestingScreen function with the touchscreen evdev id 2,
  // which maps to the secondary display.
  provider_->MoveAppToTestingScreen(/*evdev_id=*/2);

  // Confirm the app has been moved to the secondary display.
  ASSERT_EQ(secondary_display_id, screen->GetDisplayNearestWindow(window).id());

  // Call MoveAppBackToPreviousScreen to move the app back to original
  // display.
  provider_->MoveAppBackToPreviousScreen();

  // Confirm the app has been moved back to the original display.
  ASSERT_EQ(primary_display_id, screen->GetDisplayNearestWindow(window).id());
}

TEST_F(InputDataProviderTest, SetA11yTouchPassthrough) {
  aura::Window* window = widget_->GetNativeWindow();

  // The value is false in default.
  ASSERT_FALSE(window->GetProperty(
      aura::client::kAccessibilityTouchExplorationPassThrough));

  provider_->SetA11yTouchPassthrough(/*enabled=*/true);

  ASSERT_TRUE(window->GetProperty(
      aura::client::kAccessibilityTouchExplorationPassThrough));

  provider_->SetA11yTouchPassthrough(/*enabled=*/false);

  ASSERT_FALSE(window->GetProperty(
      aura::client::kAccessibilityTouchExplorationPassThrough));
}

}  // namespace diagnostics
}  // namespace ash