chromium/ash/accelerators/rapid_key_sequence_recorder_unittest.cc

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

#include "ash/accelerators/rapid_key_sequence_recorder.h"

#include "ash/public/cpp/accelerators_util.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/event_type.h"

namespace ash {

class RapidKeySequenceRecorderTest : public AshTestBase {
 public:
  RapidKeySequenceRecorderTest()
      : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  void SetUp() override {
    AshTestBase::SetUp();
    rapid_key_sequence_recorder_ = std::make_unique<RapidKeySequenceRecorder>();
    histogram_tester_ = std::make_unique<base::HistogramTester>();
  }

  void TearDown() override {
    rapid_key_sequence_recorder_.reset();
    AshTestBase::TearDown();
  }

 protected:
  std::unique_ptr<RapidKeySequenceRecorder> rapid_key_sequence_recorder_;
  std::unique_ptr<base::HistogramTester> histogram_tester_;

  void AdvanceClock(base::TimeDelta time) {
    task_environment()->AdvanceClock(time);
  }

  base::TimeTicks GetNowTimestamp() { return task_environment()->NowTicks(); }

  const ui::KeyEvent LeftShiftKeyEvent() {
    return ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_LSHIFT,
                        ui::DomCode::SHIFT_LEFT, ui::EF_NONE,
                        GetNowTimestamp());
  }
  const ui::KeyEvent RightShiftKeyEvent() {
    return ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_RSHIFT,
                        ui::DomCode::SHIFT_RIGHT, ui::EF_NONE,
                        GetNowTimestamp());
  }
};

TEST_F(RapidKeySequenceRecorderTest, SingleShiftPress) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // Wait a few seconds to reset the double-tap window.
  AdvanceClock(base::Milliseconds(3000));

  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());

  // No samples should have been recorded for any of the above events.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, LeftShiftTappedTwice) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(100));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  histogram_tester_->ExpectUniqueTimeSample(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(100), 1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 1);
  // Right shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, RightShiftTappedTwice) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(100));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", base::Milliseconds(100),
      1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 1);
  // Left shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, RightShiftOtherKeyPressedTogether) {
  const auto press_other_key_event =
      ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_A, ui::DomCode::US_A,
                   ui::EF_NONE, GetNowTimestamp());
  const auto release_other_key_event =
      ui::KeyEvent(ui::EventType::kKeyReleased, ui::VKEY_A, ui::DomCode::US_A,
                   ui::EF_NONE, GetNowTimestamp());
  const auto release_shift_key_event =
      ui::KeyEvent(ui::EventType::kKeyReleased, ui::VKEY_RSHIFT,
                   ui::DomCode::SHIFT_RIGHT, ui::EF_NONE, GetNowTimestamp());

  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      press_other_key_event);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      release_other_key_event);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      release_shift_key_event);
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      release_shift_key_event);
  // Right shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
  // Left shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, AlternatingShiftLocations) {
  // RightShift then LeftShift:
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(100));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // Wait a few seconds to reset the double-tap window.
  AdvanceClock(base::Milliseconds(3000));

  // LeftShift then RightShift:
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(100));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());

  // No metrics should have been recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, ThreeRapidPressesLeftShift) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(73));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // When three keys are consecutively pressed, only one metric for the first
  // two presses should be emitted.
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(50), 1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 1);
  // Right shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, ThreeRapidPressesRightShift) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(73));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());

  // When three keys are consecutively pressed, only one metric for the first
  // two presses should be emitted.
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", base::Milliseconds(50), 1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 1);
  // Left shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, FiveRapidPressesLeftShift) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(60));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(70));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(80));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // When five keys are consecutively pressed, two metrics (for the first
  // four presses) should be emitted.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 2);
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(50), 1);
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(70), 1);
  // Right shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, FiveRapidPressesRightShift) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(60));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(70));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(80));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());

  // When five keys are consecutively pressed, two metrics (for the first
  // four presses) should be emitted.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 2);
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", base::Milliseconds(50), 1);
  histogram_tester_->ExpectTimeBucketCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", base::Milliseconds(70), 1);
  // Left shift metric should not be recorded.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, TimingWindowCloseToLimit) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(499));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // Only record one sample (for the 499ms delay double-tap).
  histogram_tester_->ExpectUniqueTimeSample(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(499), 1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 1);
}

TEST_F(RapidKeySequenceRecorderTest, TimingWindowAtLimit) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(500));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, TimingWindowExceedsLimit) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(2000));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, StartWithShiftThenWait) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(3000));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  histogram_tester_->ExpectUniqueTimeSample(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(50), 1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 1);
}

TEST_F(RapidKeySequenceRecorderTest, StartWithShiftThenWaitThenThreeShifts) {
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(3000));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(75));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // Only one sample should be recorded.
  histogram_tester_->ExpectUniqueTimeSample(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", base::Milliseconds(50), 1);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 1);
}

TEST_F(RapidKeySequenceRecorderTest, OtherKeys) {
  ui::KeyEvent alpha_key_no_modifier(ui::EventType::kKeyPressed, ui::VKEY_C,
                                     ui::EF_NONE, GetNowTimestamp());
  // Other key, then left shift
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      alpha_key_no_modifier);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());

  // Wait a few seconds to reset the double-tap window.
  AdvanceClock(base::Milliseconds(3000));

  // Left shift, then other key
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(LeftShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      alpha_key_no_modifier);

  // Wait a few seconds to reset the double-tap window.
  AdvanceClock(base::Milliseconds(3000));

  // Other key, then right shift
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      alpha_key_no_modifier);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());

  // Wait a few seconds to reset the double-tap window.
  AdvanceClock(base::Milliseconds(3000));

  // Right shift, then other key
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      alpha_key_no_modifier);

  // Wait a few seconds to reset the double-tap window.
  AdvanceClock(base::Milliseconds(3000));

  // Right shift, then other key, then right shift again
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      alpha_key_no_modifier);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(RightShiftKeyEvent());

  // No samples should have been recorded for any of the above events.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, ShiftAndOtherModifiers) {
  ui::KeyEvent ctrl_shift_t(ui::EventType::kKeyPressed, ui::VKEY_T,
                            ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN,
                            GetNowTimestamp());

  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(ctrl_shift_t);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(ctrl_shift_t);

  // No samples should have been recorded for any of the above events.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, ShiftAndAlpha) {
  ui::KeyEvent shift_t(ui::EventType::kKeyPressed, ui::VKEY_T,
                       ui::EF_SHIFT_DOWN, GetNowTimestamp());

  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(shift_t);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(shift_t);

  // No samples should have been recorded for any of the above events.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

TEST_F(RapidKeySequenceRecorderTest, UnknownKeyAndShift) {
  ui::KeyEvent non_keypress_with_shift(ui::EventType::kKeyPressed,
                                       ui::VKEY_UNKNOWN, ui::DomCode::NONE,
                                       ui::EF_SHIFT_DOWN, GetNowTimestamp());

  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      non_keypress_with_shift);
  AdvanceClock(base::Milliseconds(50));
  rapid_key_sequence_recorder_->OnPrerewriteKeyInputEvent(
      non_keypress_with_shift);

  // No samples should have been recorded for any of the above events.
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapLeftShiftDuration", 0);
  histogram_tester_->ExpectTotalCount(
      "ChromeOS.Inputs.DoubleTapRightShiftDuration", 0);
}

}  // namespace ash