0

peripherals-metrics: Add metric to track shift double-taps

When a user double taps either shift key, if the duration between
taps was less than 500ms, a metric is recorded with that duration.

Tested manually on my DUT with internal + external keyboard.

Test: ash_unittests --gtest_filter=*RapidKeySequenceRecorderTest*
Bug: b/325098258
Change-Id: I7dcbddd3391f63004e2b1c22a70a88582152bd9a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5299522
Reviewed-by: David Padlipsky <dpad@google.com>
Reviewed-by: Zentaro Kavanagh <zentaro@chromium.org>
Reviewed-by: Jimmy Gong <jimmyxgong@chromium.org>
Reviewed-by: James Cook <jamescook@chromium.org>
Commit-Queue: Camden Bickel <cambickel@google.com>
Cr-Commit-Position: refs/heads/main@{#1261770}
This commit is contained in:
Cam Bickel
2024-02-16 18:22:39 +00:00
committed by Chromium LUCI CQ
parent 8e5028b6ab
commit 6e840fbf3f
8 changed files with 540 additions and 0 deletions

@ -72,6 +72,8 @@ component("ash") {
"accelerators/modifier_key_combo_recorder.h",
"accelerators/pre_target_accelerator_handler.cc",
"accelerators/pre_target_accelerator_handler.h",
"accelerators/rapid_key_sequence_recorder.cc",
"accelerators/rapid_key_sequence_recorder.h",
"accelerators/shortcut_input_handler.cc",
"accelerators/shortcut_input_handler.h",
"accelerators/spoken_feedback_toggler.cc",
@ -3251,6 +3253,7 @@ test("ash_unittests") {
"accelerators/ash_accelerator_configuration_unittest.cc",
"accelerators/magnifier_key_scroller_unittest.cc",
"accelerators/modifier_key_combo_recorder_unittest.cc",
"accelerators/rapid_key_sequence_recorder_unittest.cc",
"accelerators/shortcut_input_handler_unittest.cc",
"accelerators/spoken_feedback_toggler_unittest.cc",
"accelerometer/accel_gyro_samples_observer_unittest.cc",

@ -0,0 +1,95 @@
// 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 <optional>
#include <string_view>
#include <tuple>
#include "ash/events/event_rewriter_controller_impl.h"
#include "ash/events/prerewritten_event_forwarder.h"
#include "ash/public/cpp/accelerators_util.h"
#include "ash/shell.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_piece.h"
#include "base/time/time.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/types/event_type.h"
namespace ash {
namespace {
void RecordDoubleTapShiftMetric(base::TimeDelta time_between_taps,
bool is_left_shift) {
if (is_left_shift) {
UMA_HISTOGRAM_CUSTOM_TIMES("ChromeOS.Inputs.DoubleTapLeftShiftDuration",
time_between_taps, kDoubleTapShiftBucketMinimum,
kDoubleTapShiftTime,
/*bucket_count=*/kDoubleTapShiftBucketCount);
} else {
UMA_HISTOGRAM_CUSTOM_TIMES("ChromeOS.Inputs.DoubleTapRightShiftDuration",
time_between_taps, kDoubleTapShiftBucketMinimum,
kDoubleTapShiftTime,
/*bucket_count=*/kDoubleTapShiftBucketCount);
}
}
} // namespace
RapidKeySequenceRecorder::RapidKeySequenceRecorder() = default;
RapidKeySequenceRecorder::~RapidKeySequenceRecorder() {
CHECK(Shell::Get());
auto* event_forwarder =
Shell::Get()->event_rewriter_controller()->prerewritten_event_forwarder();
if (initialized_ && event_forwarder) {
event_forwarder->RemoveObserver(this);
}
}
void RapidKeySequenceRecorder::Initialize() {
CHECK(Shell::Get());
auto* event_forwarder =
Shell::Get()->event_rewriter_controller()->prerewritten_event_forwarder();
if (!event_forwarder) {
LOG(ERROR) << "Attempted to initialiaze RapidKeySequenceRecorder before "
<< "PrerewrittenEventForwarder was initialized.";
return;
}
initialized_ = true;
event_forwarder->AddObserver(this);
}
void RapidKeySequenceRecorder::OnPrerewriteKeyInputEvent(
const ui::KeyEvent& key_event) {
if (key_event.type() == ui::ET_KEY_RELEASED || key_event.is_repeat()) {
return;
}
const bool same_key_pressed_again =
key_event.code() == last_dom_code_pressed_;
const bool is_shift = key_event.code() == ui::DomCode::SHIFT_LEFT ||
key_event.code() == ui::DomCode::SHIFT_RIGHT;
last_dom_code_pressed_ = key_event.code();
if (same_key_pressed_again && is_shift) {
DCHECK(!last_key_press_time_.is_null());
auto time_since_last_shift_press =
key_event.time_stamp() - last_key_press_time_;
if (time_since_last_shift_press < kDoubleTapShiftTime) {
RecordDoubleTapShiftMetric(
/*time_between_taps=*/time_since_last_shift_press,
/*is_left_shift=*/key_event.code() == ui::DomCode::SHIFT_LEFT);
last_dom_code_pressed_ = ui::DomCode::NONE;
}
}
last_key_press_time_ = key_event.time_stamp();
}
} // namespace ash

@ -0,0 +1,49 @@
// 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.
#ifndef ASH_ACCELERATORS_RAPID_KEY_SEQUENCE_RECORDER_H_
#define ASH_ACCELERATORS_RAPID_KEY_SEQUENCE_RECORDER_H_
#include "ash/ash_export.h"
#include "ash/events/prerewritten_event_forwarder.h"
#include "base/time/time.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_handler.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace ash {
// If a second consecutive shift press happens within this time window, it is
// considered a double tap of the key.
inline constexpr base::TimeDelta kDoubleTapShiftTime = base::Milliseconds(500);
inline constexpr base::TimeDelta kDoubleTapShiftBucketMinimum =
base::Milliseconds(10);
inline constexpr int kDoubleTapShiftBucketCount = 50;
// Emits a metric when a user double taps either the left shift or right shift
// key within a short window of time.
class ASH_EXPORT RapidKeySequenceRecorder
: public PrerewrittenEventForwarder::Observer {
public:
RapidKeySequenceRecorder();
RapidKeySequenceRecorder(const RapidKeySequenceRecorder&) = delete;
RapidKeySequenceRecorder& operator=(const RapidKeySequenceRecorder&) = delete;
~RapidKeySequenceRecorder() override;
void Initialize();
// ui::PrerewrittenEventForwarder::Observer:
void OnPrerewriteKeyInputEvent(const ui::KeyEvent& event) override;
private:
bool initialized_ = false;
base::TimeTicks last_key_press_time_;
ui::DomCode last_dom_code_pressed_ = ui::DomCode::NONE;
};
} // namespace ash
#endif

@ -0,0 +1,369 @@
// 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::ET_KEY_PRESSED, ui::VKEY_LSHIFT,
ui::DomCode::SHIFT_LEFT, ui::EF_NONE,
GetNowTimestamp());
}
const ui::KeyEvent RightShiftKeyEvent() {
return ui::KeyEvent(ui::ET_KEY_PRESSED, 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, 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::ET_KEY_PRESSED, 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::ET_KEY_PRESSED, 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::ET_KEY_PRESSED, 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::ET_KEY_PRESSED, 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

@ -19,6 +19,7 @@
#include "ash/accelerators/magnifier_key_scroller.h"
#include "ash/accelerators/modifier_key_combo_recorder.h"
#include "ash/accelerators/pre_target_accelerator_handler.h"
#include "ash/accelerators/rapid_key_sequence_recorder.h"
#include "ash/accelerators/shortcut_input_handler.h"
#include "ash/accelerators/spoken_feedback_toggler.h"
#include "ash/accelerometer/accelerometer_reader.h"
@ -791,6 +792,7 @@ Shell::~Shell() {
// `shortcut_input_handler_` must be cleaned up before
// `event_rewriter_controller_`.
modifier_key_combo_recorder_.reset();
rapid_key_sequence_recorder_.reset();
shortcut_input_handler_.reset();
// `AccessibilityEventRewriter` references objects owned by
// EventRewriterController directly, so it must be reset first to avoid
@ -1376,6 +1378,7 @@ void Shell::Init(
std::make_unique<KeyboardModifierMetricsRecorder>();
event_rewriter_controller_ = std::make_unique<EventRewriterControllerImpl>();
modifier_key_combo_recorder_ = std::make_unique<ModifierKeyComboRecorder>();
rapid_key_sequence_recorder_ = std::make_unique<RapidKeySequenceRecorder>();
env_filter_ = std::make_unique<::wm::CompoundEventFilter>();
AddPreTargetHandler(env_filter_.get());

@ -218,6 +218,7 @@ class PrivacyHubController;
class PrivacyScreenController;
class ProjectingObserver;
class ProjectorControllerImpl;
class RapidKeySequenceRecorder;
class RasterScaleController;
class RgbKeyboardManager;
class ResizeShadowController;
@ -736,6 +737,9 @@ class ASH_EXPORT Shell : public SessionObserver,
ModifierKeyComboRecorder* modifier_key_combo_recorder() {
return modifier_key_combo_recorder_.get();
}
RapidKeySequenceRecorder* rapid_key_sequence_recorder() {
return rapid_key_sequence_recorder_.get();
}
ShutdownControllerImpl* shutdown_controller() {
return shutdown_controller_.get();
}
@ -969,6 +973,7 @@ class ASH_EXPORT Shell : public SessionObserver,
std::unique_ptr<InputDeviceSettingsControllerImpl>
input_device_settings_controller_;
std::unique_ptr<ModifierKeyComboRecorder> modifier_key_combo_recorder_;
std::unique_ptr<RapidKeySequenceRecorder> rapid_key_sequence_recorder_;
std::unique_ptr<InputDeviceSettingsDispatcher>
input_device_settings_dispatcher_;
std::unique_ptr<InputDeviceTracker> input_device_tracker_;

@ -11,6 +11,7 @@
#include <utility>
#include <vector>
#include "ash/accelerators/rapid_key_sequence_recorder.h"
#include "ash/accelerators/shortcut_input_handler.h"
#include "ash/components/arc/arc_features.h"
#include "ash/components/arc/arc_util.h"
@ -1349,6 +1350,7 @@ void ChromeBrowserMainPartsAsh::PostBrowserStart() {
Shell::Get()->shortcut_input_handler()->Initialize();
}
Shell::Get()->modifier_key_combo_recorder()->Initialize();
Shell::Get()->rapid_key_sequence_recorder()->Initialize();
// Enable the KeyboardDrivenEventRewriter if the OEM manifest flag is on.
if (system::InputDeviceSettings::Get()->ForceKeyboardDrivenUINavigation()) {

@ -1708,6 +1708,20 @@ They are used for the ChromeOS.CertProvisioning.* histograms to distinguish
</summary>
</histogram>
<histogram name="ChromeOS.Inputs.DoubleTap{ShiftLocation}ShiftDuration"
units="ms" expires_after="2024-08-04">
<owner>dpad@chromium.org</owner>
<owner>cros-peripherals@google.com</owner>
<summary>
Records the duration between two consecutive {ShiftLocation}Shift keypresses
(within a 500ms window).
</summary>
<token key="ShiftLocation">
<variant name="Left"/>
<variant name="Right"/>
</token>
</histogram>
<histogram name="ChromeOS.Inputs.EventRewriter.KeyRewriteLatency"
units="microseconds" expires_after="2024-07-28">
<owner>wangdanny@chromium.org</owner>