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:

committed by
Chromium LUCI CQ

parent
8e5028b6ab
commit
6e840fbf3f
@ -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",
|
||||
|
95
ash/accelerators/rapid_key_sequence_recorder.cc
Normal file
95
ash/accelerators/rapid_key_sequence_recorder.cc
Normal file
@ -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
|
49
ash/accelerators/rapid_key_sequence_recorder.h
Normal file
49
ash/accelerators/rapid_key_sequence_recorder.h
Normal file
@ -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
|
369
ash/accelerators/rapid_key_sequence_recorder_unittest.cc
Normal file
369
ash/accelerators/rapid_key_sequence_recorder_unittest.cc
Normal file
@ -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>
|
||||
|
Reference in New Issue
Block a user