chromium/chromeos/ash/components/hid_detection/bluetooth_hid_detector_impl_unittest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/hid_detection/bluetooth_hid_detector_impl.h"

#include "ash/constants/ash_features.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/hid_detection/hid_detection_utils.h"
#include "chromeos/ash/services/bluetooth_config/fake_adapter_state_controller.h"
#include "chromeos/ash/services/bluetooth_config/fake_bluetooth_power_controller.h"
#include "chromeos/ash/services/bluetooth_config/fake_device_cache.h"
#include "chromeos/ash/services/bluetooth_config/fake_device_pairing_handler.h"
#include "chromeos/ash/services/bluetooth_config/fake_discovered_devices_provider.h"
#include "chromeos/ash/services/bluetooth_config/fake_discovery_session_manager.h"
#include "chromeos/ash/services/bluetooth_config/public/mojom/cros_bluetooth_config.mojom.h"
#include "chromeos/ash/services/bluetooth_config/scoped_bluetooth_config_test_helper.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::hid_detection {

namespace {

using ::ash::hid_detection::BluetoothHidDetector;
using BluetoothHidMetadata = BluetoothHidDetector::BluetoothHidMetadata;
using BluetoothHidType = BluetoothHidDetector::BluetoothHidType;
using bluetooth_config::FakeDevicePairingHandler;
using bluetooth_config::mojom::BluetoothDeviceProperties;
using bluetooth_config::mojom::BluetoothDevicePropertiesPtr;
using bluetooth_config::mojom::BluetoothSystemState;
using bluetooth_config::mojom::DeviceType;
using bluetooth_config::mojom::PairedBluetoothDeviceProperties;
using bluetooth_config::mojom::PairedBluetoothDevicePropertiesPtr;

const char kTestPinCode[] = "123456";
const uint32_t kTestPasskey = 123456;

class FakeBluetoothHidDetectorDelegate : public BluetoothHidDetector::Delegate {
 public:
  ~FakeBluetoothHidDetectorDelegate() override = default;

  size_t num_bluetooth_hid_status_changed_calls() const {
    return num_bluetooth_hid_status_changed_calls_;
  }

 private:
  // BluetoothHidDetector::Delegate:
  void OnBluetoothHidStatusChanged() override {
    ++num_bluetooth_hid_status_changed_calls_;
  }

  size_t num_bluetooth_hid_status_changed_calls_ = 0u;
};

}  // namespace

class BluetoothHidDetectorImplTest : public testing::Test {
 public:
  BluetoothHidDetectorImplTest(const BluetoothHidDetectorImplTest&) = delete;
  BluetoothHidDetectorImplTest& operator=(const BluetoothHidDetectorImplTest&) =
      delete;

 protected:
  BluetoothHidDetectorImplTest()
      : task_environment_(
            base::test::SingleThreadTaskEnvironment::MainThreadType::UI,
            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
  ~BluetoothHidDetectorImplTest() override = default;

  // testing::Test:
  void SetUp() override {
    bluetooth_hid_detector_ = std::make_unique<BluetoothHidDetectorImpl>();
  }

  void TearDown() override {
    // HID detection must be stopped before BluetoothHidDetectorImpl is
    // destroyed.
    if (IsDiscoverySessionActive())
      StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  }

  FakeBluetoothHidDetectorDelegate* StartBluetoothHidDetection(
      bool pointer_is_missing = true,
      bool keyboard_is_missing = true) {
    delegates_.push_back(std::make_unique<FakeBluetoothHidDetectorDelegate>());
    FakeBluetoothHidDetectorDelegate* delegate = delegates_.back().get();
    bluetooth_hid_detector()->StartBluetoothHidDetection(
        delegate, {.pointer_is_missing = pointer_is_missing,
                   .keyboard_is_missing = keyboard_is_missing});
    base::RunLoop().RunUntilIdle();
    return delegate;
  }

  void StopBluetoothHidDetection(bool is_using_bluetooth) {
    bluetooth_hid_detector()->StopBluetoothHidDetection(is_using_bluetooth);
    base::RunLoop().RunUntilIdle();
  }

  void SetInputDevicesStatus(
      BluetoothHidDetector::InputDevicesStatus input_devices_status) {
    bluetooth_hid_detector()->SetInputDevicesStatus(input_devices_status);
    base::RunLoop().RunUntilIdle();
  }

  // Simulates Bluetooth being toggled by a UI surface. This sets the state of
  // the Bluetooth adapter and persists that state which is restored when HID
  // detection finishes.
  void SimulateBluetoothToggledByUi(bool enabled) {
    scoped_bluetooth_config_test_helper_.fake_bluetooth_power_controller()
        ->SetBluetoothEnabledState(enabled);
  }

  // Sets the state of the Bluetooth adapter without any persistence. This
  // simulates the adapter changing without any user interaction.
  void SetAdapterState(BluetoothSystemState system_state) {
    scoped_bluetooth_config_test_helper_.fake_adapter_state_controller()
        ->SetSystemState(system_state);
  }

  BluetoothSystemState GetAdapterState() {
    return scoped_bluetooth_config_test_helper_.fake_adapter_state_controller()
        ->GetAdapterState();
  }

  bool IsDiscoverySessionActive() {
    return scoped_bluetooth_config_test_helper_
        .fake_discovery_session_manager()
        ->IsDiscoverySessionActive();
  }

  // Updates a Bluetooth system property in order to trigger the
  // OnPropertiesUpdated() observer method in BluetoothHidDetectorImpl.
  void TriggerOnPropertiesUpdatedCall() {
    auto paired_device = PairedBluetoothDeviceProperties::New();
    paired_device->device_properties = BluetoothDeviceProperties::New();
    std::vector<PairedBluetoothDevicePropertiesPtr> paired_devices;
    paired_devices.push_back(mojo::Clone(paired_device));
    scoped_bluetooth_config_test_helper_.fake_device_cache()->SetPairedDevices(
        std::move(paired_devices));
    base::RunLoop().RunUntilIdle();
  }

  void AddUnpairedDevice(std::string* id_out, DeviceType device_type) {
    // We use the number of devices created in this test as the id.
    *id_out = base::NumberToString(num_devices_created_);
    ++num_devices_created_;

    auto device = BluetoothDeviceProperties::New();
    device->id = *id_out;
    device->public_name = base::UTF8ToUTF16(*id_out);
    device->device_type = device_type;
    unpaired_devices_.push_back(device.Clone());

    UpdateDiscoveredDevicesProviderDevices();
    base::RunLoop().RunUntilIdle();
  }

  void MockPairDeviceFinished(
      const std::string& device_id,
      FakeDevicePairingHandler* device_pairing_handler,
      std::optional<device::ConnectionFailureReason> failure_reason) {
    if (!failure_reason) {
      unpaired_devices_.erase(
          std::remove_if(
              unpaired_devices_.begin(), unpaired_devices_.end(),
              [device_id](BluetoothDevicePropertiesPtr const& device) {
                return device->id == device_id;
              }),
          unpaired_devices_.end());
      UpdateDiscoveredDevicesProviderDevices();
      base::RunLoop().RunUntilIdle();
    }

    device_pairing_handler->SimulatePairDeviceFinished(failure_reason);
    EXPECT_TRUE(device_pairing_handler->current_pairing_device_id().empty());
  }

  std::vector<raw_ptr<FakeDevicePairingHandler, VectorExperimental>>
  GetDevicePairingHandlers() {
    return scoped_bluetooth_config_test_helper_
        .fake_discovery_session_manager()
        ->device_pairing_handlers();
  }

  void AssertBluetoothHidDetectionStatus(
      std::optional<BluetoothHidMetadata> current_pairing_device,
      std::optional<BluetoothHidPairingState> pairing_state) {
    EXPECT_EQ(
        current_pairing_device.has_value(),
        GetBluetoothHidDetectionStatus().current_pairing_device.has_value());
    if (current_pairing_device.has_value()) {
      EXPECT_EQ(current_pairing_device->name,
                GetBluetoothHidDetectionStatus().current_pairing_device->name);
      EXPECT_EQ(current_pairing_device->type,
                GetBluetoothHidDetectionStatus().current_pairing_device->type);
    }

    EXPECT_EQ(pairing_state.has_value(),
              GetBluetoothHidDetectionStatus().pairing_state.has_value());
    if (pairing_state.has_value()) {
      EXPECT_EQ(pairing_state->code,
                GetBluetoothHidDetectionStatus().pairing_state->code);
      EXPECT_EQ(
          pairing_state->num_keys_entered,
          GetBluetoothHidDetectionStatus().pairing_state->num_keys_entered);
    }
  }

  void AssertBluetoothPairingResult(bool success,
                                    int count,
                                    base::TimeDelta duration) {
    histogram_tester_.ExpectTimeBucketCount(
        base::StrCat({"OOBE.HidDetectionScreen.BluetoothPairing.Duration.",
                      success ? "Success" : "Failure"}),
        duration, count);
    histogram_tester_.ExpectBucketCount(
        "OOBE.HidDetectionScreen.BluetoothPairing.Result",
        success ? HidDetectionBluetoothPairingResult::kPaired
                : HidDetectionBluetoothPairingResult::kNotPaired,
        count);
  }

  void AssertBluetoothPairingTimeoutExceeded(int count) {
    histogram_tester_.ExpectBucketCount(
        "OOBE.HidDetectionScreen.BluetoothPairing.TimeoutExceeded", true,
        count);
  }

  void AssertBluetoothPairingAttemptsCount(int bucket,
                                           int count,
                                           int total_count) {
    histogram_tester_.ExpectBucketCount(
        "OOBE.HidDetectionScreen.BluetoothPairingAttempts", bucket, count);
    histogram_tester_.ExpectTotalCount(
        "OOBE.HidDetectionScreen.BluetoothPairingAttempts", total_count);
  }

  void FastForward(base::TimeDelta time) {
    task_environment_.FastForwardBy(time);
  }

  base::TimeDelta GetMaxPairingSessionDuration() {
    return bluetooth_hid_detector_->kMaxPairingSessionDuration;
  }

 private:
  void UpdateDiscoveredDevicesProviderDevices() {
    std::vector<BluetoothDevicePropertiesPtr> unpaired_devices;
    for (auto& device : unpaired_devices_) {
      unpaired_devices.push_back(device.Clone());
    }
    scoped_bluetooth_config_test_helper_.fake_discovered_devices_provider()
        ->SetDiscoveredDevices(std::move(unpaired_devices));
  }

  const BluetoothHidDetector::BluetoothHidDetectionStatus
  GetBluetoothHidDetectionStatus() {
    return bluetooth_hid_detector()->GetBluetoothHidDetectionStatus();
  }

  BluetoothHidDetectorImpl* bluetooth_hid_detector() {
    return bluetooth_hid_detector_.get();
  }

  base::test::TaskEnvironment task_environment_;
  base::test::ScopedFeatureList scoped_feature_list_;
  base::HistogramTester histogram_tester_;

  std::vector<BluetoothDevicePropertiesPtr> unpaired_devices_;
  size_t num_devices_created_ = 0u;

  // Delegates because must be retained by the test for when HID
  // detection is stopped in TearDown().
  std::vector<std::unique_ptr<FakeBluetoothHidDetectorDelegate>> delegates_;

  bluetooth_config::ScopedBluetoothConfigTestHelper
      scoped_bluetooth_config_test_helper_;

  std::unique_ptr<hid_detection::BluetoothHidDetectorImpl>
      bluetooth_hid_detector_;
};

TEST_F(BluetoothHidDetectorImplTest, StartStopStartDetection_BluetoothEnabled) {
  // Start with Bluetooth enabled.
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Trigger an OnPropertiesUpdated() call. Nothing should happen.
  TriggerOnPropertiesUpdatedCall();
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Begin HID detection. Discovery should have started and Bluetooth still
  // enabled.
  StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());

  // Trigger an OnPropertiesUpdated() call. Nothing should happen.
  TriggerOnPropertiesUpdatedCall();
  EXPECT_TRUE(IsDiscoverySessionActive());

  // Stop HID detection. Discovery should have stopped but Bluetooth still
  // enabled.
  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  EXPECT_FALSE(IsDiscoverySessionActive());
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
  AssertBluetoothPairingAttemptsCount(/*bucket=*/0, /*count=*/1,
                                      /*total_count=*/1);

  // Trigger an OnPropertiesUpdated() call. Nothing should happen.
  TriggerOnPropertiesUpdatedCall();
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Begin HID detection again. Discovery should have started and Bluetooth
  // still enabled.
  StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
}

TEST_F(BluetoothHidDetectorImplTest,
       StartStopDetection_BluetoothDisabled_BluetoothUnused) {
  // Initiate disabling Bluetooth.
  SimulateBluetoothToggledByUi(/*enabled=*/false);
  EXPECT_EQ(BluetoothSystemState::kDisabling, GetAdapterState());

  // Complete adapter disabling.
  SetAdapterState(BluetoothSystemState::kDisabled);
  EXPECT_EQ(BluetoothSystemState::kDisabled, GetAdapterState());

  // Begin HID detection. The adapter state should switch to enabling.
  StartBluetoothHidDetection();
  EXPECT_EQ(BluetoothSystemState::kEnabling, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Mock the adapter becoming unavailable.
  SetAdapterState(BluetoothSystemState::kUnavailable);
  EXPECT_EQ(BluetoothSystemState::kUnavailable, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Mock the adapter becoming available again.
  SetAdapterState(BluetoothSystemState::kEnabling);
  EXPECT_EQ(BluetoothSystemState::kEnabling, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Mock the adapter enabling. Discovery should have started.
  SetAdapterState(BluetoothSystemState::kEnabled);
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
  EXPECT_TRUE(IsDiscoverySessionActive());

  // Trigger an OnPropertiesUpdated() call. Nothing should happen.
  TriggerOnPropertiesUpdatedCall();

  // Stop HID detection with no device using Bluetooth. Discovery should have
  // stopped and Bluetooth disabled.
  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  EXPECT_FALSE(IsDiscoverySessionActive());
  EXPECT_EQ(BluetoothSystemState::kDisabling, GetAdapterState());
}

TEST_F(BluetoothHidDetectorImplTest,
       StartStopDetection_BluetoothDisabled_BluetoothUsed) {
  // Initiate disabling Bluetooth.
  SimulateBluetoothToggledByUi(/*enabled=*/false);
  EXPECT_EQ(BluetoothSystemState::kDisabling, GetAdapterState());

  // Complete adapter disabling.
  SetAdapterState(BluetoothSystemState::kDisabled);
  EXPECT_EQ(BluetoothSystemState::kDisabled, GetAdapterState());

  // Begin HID detection. The adapter state should switch to enabling.
  StartBluetoothHidDetection();
  EXPECT_EQ(BluetoothSystemState::kEnabling, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Stop HID detection with Bluetooth being used by a device. Discovery should
  // have stopped but the adapter state remained the same.
  StopBluetoothHidDetection(/*is_using_bluetooth=*/true);
  EXPECT_FALSE(IsDiscoverySessionActive());
  EXPECT_EQ(BluetoothSystemState::kEnabling, GetAdapterState());
}

TEST_F(BluetoothHidDetectorImplTest, StartDetection_BluetoothUnavailable) {
  // Set Bluetooth to unavailable.
  SetAdapterState(BluetoothSystemState::kUnavailable);
  EXPECT_EQ(BluetoothSystemState::kUnavailable, GetAdapterState());

  // Begin HID detection.
  StartBluetoothHidDetection();
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Set Bluetooth to enabling.
  SetAdapterState(BluetoothSystemState::kEnabling);
  EXPECT_EQ(BluetoothSystemState::kEnabling, GetAdapterState());

  // Complete adapter enabling. Discovery should have started.
  SetAdapterState(BluetoothSystemState::kEnabled);
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
  EXPECT_TRUE(IsDiscoverySessionActive());
}

TEST_F(BluetoothHidDetectorImplTest,
       StartDetection_BluetoothDisabledEnabledExternally) {
  // Start with Bluetooth enabled.
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());

  // Begin HID detection. Discovery should have started and Bluetooth still
  // enabled.
  StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());

  // Mock another client disabling Bluetooth.
  SimulateBluetoothToggledByUi(/*enabled=*/false);
  EXPECT_EQ(BluetoothSystemState::kDisabling, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Finish the adapter disabling.
  SetAdapterState(BluetoothSystemState::kDisabled);
  EXPECT_EQ(BluetoothSystemState::kDisabled, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());

  // Mock another client re-enabling Bluetooth. This should cause
  // BluetoothHidDetector to start discovery again.
  SimulateBluetoothToggledByUi(/*enabled=*/true);
  EXPECT_EQ(BluetoothSystemState::kEnabling, GetAdapterState());
  EXPECT_FALSE(IsDiscoverySessionActive());
  SetAdapterState(BluetoothSystemState::kEnabled);
  EXPECT_EQ(BluetoothSystemState::kEnabled, GetAdapterState());
  EXPECT_TRUE(IsDiscoverySessionActive());
}

TEST_F(BluetoothHidDetectorImplTest, AddDevices_TypeNotHid) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kHeadset);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should not be attempted to be paired
  // with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, AddDevices_TypeNotMissing) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should not be attempted to be paired
  // with.
  FakeBluetoothHidDetectorDelegate* delegate =
      StartBluetoothHidDetection(/*is_pointer_missing=*/false);
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, AddDevices_NoTypeMissing) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection with no types missing. No device should be attempted to
  // be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection(
      /*is_pointer_missing=*/false, /*is_keyboard_missing=*/false);
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(0u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Mock the pointer disconnecting and add another device to trigger
  // OnDiscoveredDevicesListChanged(). |device_id1| should be attempted to be
  // paired with.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = false});
  std::string device_id3;
  AddUnpairedDevice(&device_id3, DeviceType::kMouse);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest,
       AddDevices_SeriallyAfterStartingDetection) {
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(0u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kTablet);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id1| being paired. BluetoothHidDetectorImpl should not inform
  // the delegate or move to the next device in queue until the input devices
  // status has been updated.
  MockPairDeviceFinished(device_id1, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Mock |device_id1| being registered as connected. The next device in the
  // queue should now be processed.
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = true});
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, AddDevices_BatchAfterStartingDetection) {
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(0u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboardMouseCombo);
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id1| being paired. BluetoothHidDetectorImpl should not inform
  // the delegate or move to the next device in queue until the input devices
  // status has been updated.
  MockPairDeviceFinished(device_id1, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Mock |device_id1| being registered as connected. |device_id2| should be
  // attempted to be paired with.
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = true});
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboardPointerCombo),
      /*pairing_state=*/std::nullopt);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id2| being registered as connected. Two devices should be
  // paired successfully.
  MockPairDeviceFinished(device_id2, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboardPointerCombo),
      /*pairing_state=*/std::nullopt);

  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = false});
  EXPECT_EQ(4u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/2,
                               GetMaxPairingSessionDuration() / 2);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest,
       AddDevices_BeforeStartingDetectionSameType) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kMouse);

  std::string device_id3;
  AddUnpairedDevice(&device_id3, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id1| being paired. BluetoothHidDetectorImpl should not inform
  // the delegate or move to the next device in queue until the input devices
  // status has been updated.
  MockPairDeviceFinished(device_id1, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Mock |device_id1| being registered as connected. |device_id3| should be
  // attempted to be paired with.
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = true});
  EXPECT_EQ(device_id3,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id3, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);
  AssertBluetoothPairingResult(/*success=*/false, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id3| pairing failing. BluetoothHidDetectorImpl should move to
  // the next device in the queue immediately.
  MockPairDeviceFinished(device_id3, GetDevicePairingHandlers()[0],
                         device::ConnectionFailureReason::kFailed);
  EXPECT_EQ(4u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/false, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, DisconnectDevice) {
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(0u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Set both devices to connected.
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = false});
  EXPECT_EQ(0u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Add a discovered device. Nothing should happen.
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(0u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Mock the pointer no longer being connected.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = false});

  // Add another device to trigger OnDiscoveredDevicesListChanged().
  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, ConnectDeviceTypeDuringPairing) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Mock a keyboard being connected. Nothing should happen.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = false});
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Mock keyboard being disconnected. Nothing should happen.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = true});
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Mock a pointer being connected. This should cancel pairing with
  // |device_id1|.
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = true});
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest,
       ConnectDeviceTypeDuringKeyboardMouseComboPairing) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kKeyboardMouseCombo);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kKeyboardPointerCombo),
      /*pairing_state=*/std::nullopt);

  // Mock a keyboard being connected. This should not cancel pairing with
  // |device_id1|.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = false});
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kKeyboardPointerCombo),
      /*pairing_state=*/std::nullopt);

  // Mock a pointer also being connected. This should cancel pairing with
  // |device_id1|.
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = false});
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, AdapterDisablesDuringPairing) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Simulate "DisplayPasskey" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPasskey(kTestPasskey);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  // Mock the adapter disabling.
  SetAdapterState(BluetoothSystemState::kDisabled);
  EXPECT_FALSE(IsDiscoverySessionActive());
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Mock the adapter re-enabling Bluetooth. This should cause
  // BluetoothHidDetector to start discovery again. The first device should be
  // attempted to be paired with again.
  SetAdapterState(BluetoothSystemState::kEnabled);
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(2u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[1]->current_pairing_device_id());
  EXPECT_EQ(4u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Simulate "DisplayPincode" authorization required.
  GetDevicePairingHandlers()[1]->SimulateDisplayPinCode(kTestPinCode);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[1]->current_pairing_device_id());
  EXPECT_EQ(5u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, DetectionStopsStartsDuringPairing) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate1 = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate1->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Simulate "DisplayPincode" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPinCode(kTestPinCode);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(2u, delegate1->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  // Stop detection.
  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
  EXPECT_FALSE(IsDiscoverySessionActive());
  EXPECT_EQ(3u, delegate1->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Start detection again. The first device should be attempted to be paired
  // with again.
  FakeBluetoothHidDetectorDelegate* delegate2 = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(2u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[1]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate2->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Simulate "DisplayPasskey" authorization required.
  GetDevicePairingHandlers()[1]->SimulateDisplayPasskey(kTestPasskey);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[1]->current_pairing_device_id());
  EXPECT_EQ(2u, delegate2->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/2,
                                      /*total_count=*/2);
}

TEST_F(BluetoothHidDetectorImplTest, AddDevices_UnsupportedAuthorizations) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kTablet);

  std::string device_id3;
  AddUnpairedDevice(&device_id3, DeviceType::kKeyboardMouseCombo);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Simulate "RequestPinCode" authorization required. This should cancel the
  // pairing. |device_id2| should be attempted to be paired with.
  GetDevicePairingHandlers()[0]->SimulateRequestPinCode();
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);

  // Simulate "RequestPasskey" authorization required. This should cancel the
  // pairing. |device_id3| should be attempted to be paired with.
  GetDevicePairingHandlers()[0]->SimulateRequestPasskey();
  EXPECT_EQ(device_id3,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(5u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id3, BluetoothHidType::kKeyboardPointerCombo),
      /*pairing_state=*/std::nullopt);

  // Simulate "ConfirmPasskey" authorization required. This should cancel the
  // pairing.
  GetDevicePairingHandlers()[0]->SimulateConfirmPasskey(kTestPasskey);
  EXPECT_FALSE(GetDevicePairingHandlers()[0]->last_confirm().has_value());
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(6u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/3, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, AddDevice_AuthorizePairingAuth) {
  std::string device_id;
  AddUnpairedDevice(&device_id, DeviceType::kKeyboard);

  // Begin HID detection. |device_id| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);
  EXPECT_FALSE(GetDevicePairingHandlers()[0]->last_confirm());

  // Simulate "AuthorizePairing" authorization required. The pairing should be
  // automatically authorized. BluetoothHidDetectorImpl won't move onto the next
  // device because the device has not been registered as connected yet.
  GetDevicePairingHandlers()[0]->SimulateAuthorizePairing();
  EXPECT_TRUE(GetDevicePairingHandlers()[0]->last_confirm().has_value());
  EXPECT_TRUE(GetDevicePairingHandlers()[0]->last_confirm().value());
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  // Mock the device being registered as connected.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = false});
  EXPECT_TRUE(
      GetDevicePairingHandlers()[0]->current_pairing_device_id().empty());
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/1, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, AddDevice_DisplayCodeAuths) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kKeyboard);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kMouse);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  // Simulate "DisplayPinCode" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPinCode(kTestPinCode);
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kKeyboard),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  // Simulate keys being entered consecutively. The delegate should be informed
  // each time.
  for (uint32_t num_keys_entered = 1;
       num_keys_entered <= std::strlen(kTestPinCode); num_keys_entered++) {
    GetDevicePairingHandlers()[0]->SimulateKeysEntered(num_keys_entered);
    EXPECT_EQ(device_id1,
              GetDevicePairingHandlers()[0]->current_pairing_device_id());
    EXPECT_EQ(2u + num_keys_entered,
              delegate->num_bluetooth_hid_status_changed_calls());
    AssertBluetoothHidDetectionStatus(
        BluetoothHidMetadata(device_id1, BluetoothHidType::kKeyboard),
        BluetoothHidPairingState(kTestPinCode, num_keys_entered));
  }
  EXPECT_EQ(8u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id1| being paired. BluetoothHidDetectorImpl should not inform
  // the delegate or move to the next device in queue until the input devices
  // status has been updated.
  MockPairDeviceFinished(device_id1, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  EXPECT_EQ(8u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kKeyboard),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/6u));

  // Mock |device_id1| being registered as connected. |device_id2| should be
  // attempted to be paired with.
  SetInputDevicesStatus(
      {.pointer_is_missing = true, .keyboard_is_missing = false});
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(10u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);

  // Simulate "DisplayPasskey" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPasskey(kTestPasskey);
  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(11u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  // Simulate keys being entered consecutively. The delegate should be informed
  // each time.
  for (uint32_t num_keys_entered = 1; num_keys_entered <= 6u;
       num_keys_entered++) {
    GetDevicePairingHandlers()[0]->SimulateKeysEntered(num_keys_entered);
    EXPECT_EQ(device_id2,
              GetDevicePairingHandlers()[0]->current_pairing_device_id());
    EXPECT_EQ(11u + num_keys_entered,
              delegate->num_bluetooth_hid_status_changed_calls());
    AssertBluetoothHidDetectionStatus(
        BluetoothHidMetadata(device_id2, BluetoothHidType::kPointer),
        BluetoothHidPairingState(kTestPinCode, num_keys_entered));
  }
  EXPECT_EQ(17u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/false, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  // Mock time passing to measure pairing duration.
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock |device_id2| pairing failing.
  MockPairDeviceFinished(device_id2, GetDevicePairingHandlers()[0],
                         device::ConnectionFailureReason::kAuthFailed);
  EXPECT_EQ(18u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(/*current_pairing_device=*/std::nullopt,
                                    /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/false, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  AssertBluetoothPairingAttemptsCount(/*bucket=*/2, /*count=*/1,
                                      /*total_count=*/1);
}

TEST_F(BluetoothHidDetectorImplTest, PairingTimesOut) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  std::string device_id2;
  AddUnpairedDevice(&device_id2, DeviceType::kKeyboard);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());

  // Simulate "DisplayPasskey" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPasskey(kTestPasskey);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  // Fast forward past the pairing timeout period. The pairing state should
  // reset and the second device should be pairing.
  FastForward(GetMaxPairingSessionDuration());
  AssertBluetoothPairingTimeoutExceeded(/*count=*/1);

  EXPECT_EQ(device_id2,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  EXPECT_EQ(4u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  FastForward(GetMaxPairingSessionDuration() / 2);
  MockPairDeviceFinished(device_id2, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/1);

  EXPECT_EQ(4u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id2, BluetoothHidType::kKeyboard),
      /*pairing_state=*/std::nullopt);

  // Fast forward past the pairing timeout period. This should cancel the
  // current pairing, even if it was a success, because SetInputDevicesStatus()
  // hadn't been called. The pairing state should move on to pairing the
  // |device_id1| again.
  FastForward(GetMaxPairingSessionDuration() / 2);
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  EXPECT_EQ(6u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/2);
}

TEST_F(BluetoothHidDetectorImplTest, TimeoutTimerCancelledOnFailure) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());

  // Simulate "DisplayPasskey" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPasskey(kTestPasskey);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);

  // Mock |device_id1| as failed pairing.
  MockPairDeviceFinished(device_id1, GetDevicePairingHandlers()[0],
                         device::ConnectionFailureReason::kAuthFailed);
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Advance the rest of the timeout duration. If the timer from the last
  // pairing was not cancelled, this should cause a crash.
  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);
}

TEST_F(BluetoothHidDetectorImplTest, TimeoutTimerCancelledOnSuccess) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());

  // Simulate "DisplayPasskey" authorization required.
  GetDevicePairingHandlers()[0]->SimulateDisplayPasskey(kTestPasskey);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));

  // Mock |device_id1| being paired. BluetoothHidDetectorImpl should not inform
  // the delegate or move to the next device in queue until the input devices
  // status has been updated.
  MockPairDeviceFinished(device_id1, GetDevicePairingHandlers()[0],
                         /*failure_reason=*/std::nullopt);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      BluetoothHidPairingState(kTestPinCode, /*num_keys_entered=*/0u));
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/1,
                               GetMaxPairingSessionDuration() / 2);
  SetInputDevicesStatus(
      {.pointer_is_missing = false, .keyboard_is_missing = true});
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);

  // Advance the rest of the timeout duration. If the timer from the last
  // pairing was not cancelled, this should cause a crash.
  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(3u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);
}

TEST_F(BluetoothHidDetectorImplTest,
       TimeoutTimerCancelledOnHidDetectionStopped) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate1 = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(1u, delegate1->num_bluetooth_hid_status_changed_calls());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);

  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(1u, delegate1->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);

  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
  EXPECT_FALSE(IsDiscoverySessionActive());
  EXPECT_EQ(2u, delegate1->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);

  // Advance the rest of the timeout duration. If the timer from the previous
  // pairing was not cancelled, this should cause a crash.
  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(2u, delegate1->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);
}

TEST_F(BluetoothHidDetectorImplTest, TimeoutTimerCancelledOnBluetoothDisabled) {
  std::string device_id1;
  AddUnpairedDevice(&device_id1, DeviceType::kMouse);

  // Begin HID detection. |device_id1| should be attempted to be paired with.
  FakeBluetoothHidDetectorDelegate* delegate = StartBluetoothHidDetection();
  EXPECT_TRUE(IsDiscoverySessionActive());
  EXPECT_EQ(1u, GetDevicePairingHandlers().size());
  EXPECT_EQ(1u, delegate->num_bluetooth_hid_status_changed_calls());
  EXPECT_EQ(device_id1,
            GetDevicePairingHandlers()[0]->current_pairing_device_id());
  AssertBluetoothHidDetectionStatus(
      BluetoothHidMetadata(device_id1, BluetoothHidType::kPointer),
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingResult(/*success=*/true, /*count=*/0,
                               GetMaxPairingSessionDuration() / 2);
  FastForward(GetMaxPairingSessionDuration() / 2);

  // Mock the adapter disabling. The timeout should be stopped.
  SetAdapterState(BluetoothSystemState::kDisabled);
  EXPECT_FALSE(IsDiscoverySessionActive());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);

  // Advance the rest of the timeout duration. If the timer from the previous
  // pairing was not cancelled, this should cause a crash.
  FastForward(GetMaxPairingSessionDuration() / 2);
  EXPECT_EQ(2u, delegate->num_bluetooth_hid_status_changed_calls());
  AssertBluetoothHidDetectionStatus(
      /*current_pairing_device=*/std::nullopt,
      /*pairing_state=*/std::nullopt);
  AssertBluetoothPairingTimeoutExceeded(/*count=*/0);

  // HID detection must be stopped before BluetoothHidDetectorImpl is destroyed.
  StopBluetoothHidDetection(/*is_using_bluetooth=*/false);
}

}  // namespace ash::hid_detection