diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-24 05:05:13 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-09-24 05:05:13 +0000 |
commit | 9850e2d4cbcd86fde2fc6ba8b3704e7abef9c841 (patch) | |
tree | 3af5ed9cb9d4667f9e379c277a2ac9755efedc2c | |
parent | b48b247096f6e71491b8a5e0605bbefd72105aa4 (diff) | |
parent | 9b40752880ad12a6afd219e0c0f97d9933f1a174 (diff) | |
download | libpalmrejection-android13-qpr1-release.tar.gz |
Snap for 9101843 from 9b40752880ad12a6afd219e0c0f97d9933f1a174 to tm-qpr1-releaseandroid-13.0.0_r30android-13.0.0_r29android-13.0.0_r28android-13.0.0_r27android-13.0.0_r24android-13.0.0_r23android-13.0.0_r22android-13.0.0_r21android-13.0.0_r20android-13.0.0_r19android-13.0.0_r18android-13.0.0_r17android-13.0.0_r16android13-qpr1-s8-releaseandroid13-qpr1-s7-releaseandroid13-qpr1-s6-releaseandroid13-qpr1-s5-releaseandroid13-qpr1-s4-releaseandroid13-qpr1-s3-releaseandroid13-qpr1-s2-releaseandroid13-qpr1-s1-releaseandroid13-qpr1-release
Change-Id: I5fdc77a724fd655d9848a5717c5d74be3bce28dc
14 files changed, 337 insertions, 155 deletions
@@ -18,6 +18,7 @@ cc_library_static { local_include_dirs: ["."], export_include_dirs: ["."], srcs: [ + "chrome_to_android_compatibility.cc", "ui/events/ozone/features.cc", "ui/events/ozone/evdev/touch_evdev_types.cc", "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc", diff --git a/chrome_to_android_compatibility.cc b/chrome_to_android_compatibility.cc new file mode 100644 index 0000000..18b5a80 --- /dev/null +++ b/chrome_to_android_compatibility.cc @@ -0,0 +1,15 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome_to_android_compatibility.h" + +// Android's external/libchrome directory is out of date. +// Add missing templates here as a temporary solution +namespace base { + +bool operator==(const TimeTicks& t1, const TimeTicks& t2) { + return t1.since_origin() == t2.since_origin(); +} + +} // namespace base diff --git a/chrome_to_android_compatibility.h b/chrome_to_android_compatibility.h index f51ea36..91c442a 100644 --- a/chrome_to_android_compatibility.h +++ b/chrome_to_android_compatibility.h @@ -10,6 +10,14 @@ // Add missing templates here as a temporary solution namespace base { +/** + * Workaround for the error in unit tests: ISO C++20 considers use of overloaded + * operator '==' (with operand types 'const base::TimeTicks' + * and 'const base::TimeTicks') to be ambiguous despite there being a unique + * best viable function [-Werror,-Wambiguous-reversed-operator] + */ +bool operator==(const TimeTicks& t1, const TimeTicks& t2); + namespace time_internal { // clang-format off diff --git a/chrome_to_android_compatibility_test_support.cc b/chrome_to_android_compatibility_test_support.cc index 5aa94b9..d1fe1e5 100644 --- a/chrome_to_android_compatibility_test_support.cc +++ b/chrome_to_android_compatibility_test_support.cc @@ -6,12 +6,6 @@ #include "base/time/time.h" -namespace base { -bool operator==(const TimeTicks& t1, const TimeTicks& t2) { - return t1.since_origin() == t2.since_origin(); -} -} // namespace base - namespace gfx { // clang-format off diff --git a/chrome_to_android_compatibility_test_support.h b/chrome_to_android_compatibility_test_support.h deleted file mode 100644 index c2e4c90..0000000 --- a/chrome_to_android_compatibility_test_support.h +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#pragma once - -#include "base/time/time.h" - -namespace base { - -/** - * Workaround for the error in unit tests: ISO C++20 considers use of overloaded - * operator '==' (with operand types 'const base::TimeTicks' - * and 'const base::TimeTicks') to be ambiguous despite there being a unique - * best viable function [-Werror,-Wambiguous-reversed-operator] - */ -bool operator==(const TimeTicks& t1, const TimeTicks& t2); - -} // namespace base diff --git a/copy.bara.sky b/copy.bara.sky index c270d38..cb88b0c 100644 --- a/copy.bara.sky +++ b/copy.bara.sky @@ -90,9 +90,9 @@ core.workflow( "OWNERS", "PREUPLOAD.cfg", "TEST_MAPPING", + "chrome_to_android_compatibility.cc", "chrome_to_android_compatibility.h", "chrome_to_android_compatibility_test_support.cc", - "chrome_to_android_compatibility_test_support.h", "copy.bara.sky", ], ), diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc index 0b301e4..9a8e385 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.cc @@ -37,20 +37,49 @@ float EuclideanDistance(const gfx::PointF& a, const gfx::PointF& b) { bool IsEarlyStageSample( const PalmFilterStroke& stroke, const NeuralStylusPalmDetectionFilterModelConfig& config) { - return config.early_stage_sample_counts.find(stroke.samples_seen()) != - config.early_stage_sample_counts.end(); + if (!config.resample_period) { + return config.early_stage_sample_counts.find(stroke.samples_seen()) != + config.early_stage_sample_counts.end(); + } + // Duration is not well-defined for sample_count <= 1, so we handle + // it separately. + if (stroke.samples().empty()) { + return false; + } + if (stroke.samples().size() == 1) { + return config.early_stage_sample_counts.find(1) != + config.early_stage_sample_counts.end(); + } + for (const uint32_t sample_count : config.early_stage_sample_counts) { + const base::TimeDelta duration = config.GetEquivalentDuration(sample_count); + // Previous sample must not have passed the 'duration' threshold, but the + // current sample must pass the threshold + if (stroke.LastSampleCrossed(duration)) { + return true; + } + } + return false; } bool HasDecidedStroke( const PalmFilterStroke& stroke, const NeuralStylusPalmDetectionFilterModelConfig& config) { - return stroke.samples_seen() >= config.max_sample_count; + if (!config.resample_period) { + return stroke.samples_seen() >= config.max_sample_count; + } + const base::TimeDelta max_duration = + config.GetEquivalentDuration(config.max_sample_count); + return stroke.Duration() >= max_duration; } bool IsVeryShortStroke( const PalmFilterStroke& stroke, const NeuralStylusPalmDetectionFilterModelConfig& config) { - return stroke.samples_seen() < config.min_sample_count; + if (!config.resample_period) { + return stroke.samples_seen() < config.min_sample_count; + } + return stroke.Duration() < + config.GetEquivalentDuration(config.min_sample_count); } /** @@ -58,9 +87,15 @@ bool IsVeryShortStroke( * being evaluated. The parameter 'neighbor_min_sample_count' might be different * from the config, depending on the specific usage in the caller. */ -bool HasInsufficientDataAsNeighbor(const PalmFilterStroke& neighbor_stroke, - size_t neighbor_min_sample_count) { - return neighbor_stroke.samples().size() < neighbor_min_sample_count; +bool HasInsufficientDataAsNeighbor( + const PalmFilterStroke& neighbor_stroke, + size_t neighbor_min_sample_count, + const NeuralStylusPalmDetectionFilterModelConfig& config) { + if (!config.resample_period) { + return neighbor_stroke.samples().size() < neighbor_min_sample_count; + } + return neighbor_stroke.Duration() < + config.GetEquivalentDuration(neighbor_min_sample_count); } } // namespace @@ -105,7 +140,8 @@ void NeuralStylusPalmDetectionFilter::FindBiggestNeighborsWithin( if (neighbor.tracking_id() == stroke.tracking_id()) { continue; } - if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count)) { + if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count, + model_->config())) { continue; } float distance = @@ -145,7 +181,8 @@ void NeuralStylusPalmDetectionFilter::FindNearestNeighborsWithin( if (neighbor.tracking_id() == stroke.tracking_id()) { continue; } - if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count)) { + if (HasInsufficientDataAsNeighbor(neighbor, neighbor_min_sample_count, + model_->config())) { continue; } float distance = @@ -300,15 +337,29 @@ bool NeuralStylusPalmDetectionFilter::ShouldDecideStroke( const PalmFilterStroke& stroke) const { const NeuralStylusPalmDetectionFilterModelConfig& config = model_->config(); // Inference only executed once per stroke - return stroke.samples_seen() == config.max_sample_count; + if (!config.resample_period) { + return stroke.samples_seen() == config.max_sample_count; + } + return stroke.LastSampleCrossed( + config.GetEquivalentDuration(config.max_sample_count)); } bool NeuralStylusPalmDetectionFilter::IsHeuristicPalmStroke( const PalmFilterStroke& stroke) const { const auto& config = model_->config(); - if (stroke.samples().size() >= config.max_sample_count) { - LOG(DFATAL) << "Should not call this method on long strokes."; - return false; + if (config.resample_period) { + if (stroke.Duration() > + config.GetEquivalentDuration(config.max_sample_count)) { + LOG(DFATAL) + << "Should not call this method on long strokes. Got duration = " + << stroke.Duration(); + return false; + } + } else { + if (stroke.samples().size() >= config.max_sample_count) { + LOG(DFATAL) << "Should not call this method on long strokes."; + return false; + } } if (config.heuristic_palm_touch_limit > 0.0) { @@ -401,6 +452,9 @@ std::vector<float> NeuralStylusPalmDetectionFilter::ExtractFeatures( void NeuralStylusPalmDetectionFilter::AppendFeatures( const PalmFilterStroke& stroke, std::vector<float>* features) const { + if (model_->config().resample_period) { + return AppendResampledFeatures(stroke, features); + } const int size = stroke.samples().size(); for (int i = 0; i < size; ++i) { const PalmFilterSample& sample = stroke.samples()[i]; @@ -435,6 +489,59 @@ void NeuralStylusPalmDetectionFilter::AppendFeatures( features->push_back(samples_seen - model_->config().max_sample_count); } } + +/** + * The flow here is similar to 'AppendFeatures' above, but we rely on the + * timing of the samples rather than on the explicit number / position of + * samples. + */ +void NeuralStylusPalmDetectionFilter::AppendResampledFeatures( + const PalmFilterStroke& stroke, + std::vector<float>* features) const { + size_t sample_count = 0; + const base::TimeTicks& first_time = stroke.samples()[0].time; + const base::TimeDelta& resample_period = *model_->config().resample_period; + const base::TimeDelta max_duration = + model_->config().GetEquivalentDuration(model_->config().max_sample_count); + for (auto time = first_time; (time - first_time) <= max_duration && + time <= stroke.samples().back().time; + time += resample_period) { + sample_count++; + const PalmFilterSample& sample = stroke.GetSampleAt(time); + features->push_back(sample.major_radius); + features->push_back(sample.minor_radius <= 0.0 ? sample.major_radius + : sample.minor_radius); + float distance = 0; + if (time != first_time) { + distance = EuclideanDistance( + stroke.GetSampleAt(time - resample_period).point, sample.point); + } + features->push_back(distance); + features->push_back(sample.edge); + features->push_back(1.0); // existence. + } + const int padding = model_->config().max_sample_count - sample_count; + DCHECK_GE(padding, 0); + + for (int i = 0; i < padding * kFeaturesPerSample; ++i) { + features->push_back(0.0); + } + // "fill proportion." + features->push_back(static_cast<float>(sample_count) / + model_->config().max_sample_count); + features->push_back(EuclideanDistance(stroke.samples().front().point, + stroke.samples().back().point)); + + // Start sequence number. 0 is min. + uint32_t samples_seen = + (stroke.Duration() / (*model_->config().resample_period)) + 1; + if (samples_seen < model_->config().max_sample_count) { + features->push_back(0); + } else { + features->push_back(samples_seen - model_->config().max_sample_count); + } +} + void NeuralStylusPalmDetectionFilter::AppendFeaturesAsNeighbor( const PalmFilterStroke& stroke, float distance, diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h index bab332e..c27e9e9 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h @@ -109,6 +109,8 @@ class COMPONENT_EXPORT(EVDEV) NeuralStylusPalmDetectionFilter std::vector<float> ExtractFeatures(int tracking_id) const; void AppendFeatures(const PalmFilterStroke& stroke, std::vector<float>* features) const; + void AppendResampledFeatures(const PalmFilterStroke& stroke, + std::vector<float>* features) const; void AppendFeaturesAsNeighbor(const PalmFilterStroke& stroke, float distance, std::vector<float>* features) const; diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc index da596b8..51aa4df 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.cc @@ -4,6 +4,8 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h" +#include "base/logging.h" + namespace ui { NeuralStylusPalmDetectionFilterModelConfig:: @@ -15,4 +17,19 @@ NeuralStylusPalmDetectionFilterModelConfig:: NeuralStylusPalmDetectionFilterModelConfig:: ~NeuralStylusPalmDetectionFilterModelConfig() = default; + +base::TimeDelta +NeuralStylusPalmDetectionFilterModelConfig::GetEquivalentDuration( + uint32_t sample_count) const { + if (!resample_period) { + LOG(DFATAL) << __func__ + << " should only be called if resampling is enabled"; + return base::TimeDelta::FromMicroseconds(0); + } + if (sample_count <= 1) { + return base::TimeDelta::FromMicroseconds(0); + } + return (sample_count - 1) * (*resample_period); +} + } // namespace ui
\ No newline at end of file diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h index dab5455..bcc21b2 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h @@ -42,6 +42,10 @@ struct COMPONENT_EXPORT(EVDEV) NeuralStylusPalmDetectionFilterModelConfig { // Maximum sample count. uint32_t max_sample_count = 0; + // Convert the provided 'sample_count' to an equivalent time duration. + // Should only be called when resampling is enabled. + base::TimeDelta GetEquivalentDuration(uint32_t sample_count) const; + // Minimum count of samples for a stroke to be considered as a neighbor. uint32_t neighbor_min_sample_count = 0; diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc index ce250f7..13288f0 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_unittest.cc @@ -14,7 +14,7 @@ #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) #include "ui/events/ozone/evdev/event_device_test_util.h" #else -#include "chrome_to_android_compatibility_test_support.h" +#include "chrome_to_android_compatibility.h" #endif #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_model.h" #include "ui/events/ozone/evdev/touch_filter/palm_detection_filter.h" @@ -29,7 +29,8 @@ class MockNeuralModel : public NeuralStylusPalmDetectionFilterModel { const NeuralStylusPalmDetectionFilterModelConfig&()); }; -class NeuralStylusPalmDetectionFilterTest : public testing::Test { +class NeuralStylusPalmDetectionFilterTest + : public testing::TestWithParam<float> { public: NeuralStylusPalmDetectionFilterTest() = default; @@ -48,6 +49,11 @@ class NeuralStylusPalmDetectionFilterTest : public testing::Test { model_config_.heuristic_palm_touch_limit = 40; model_config_.heuristic_palm_area_limit = 1000; model_config_.max_dead_neighbor_time = base::Milliseconds(100); + const float resample_period = GetParam(); + if (resample_period != 0.0) { + model_config_.resample_period = base::Milliseconds(resample_period); + sample_period_ = *model_config_.resample_period; + } EXPECT_CALL(*model_, config()) .Times(testing::AnyNumber()) .WillRepeatedly(testing::ReturnRef(model_config_)); @@ -87,13 +93,19 @@ class NeuralStylusPalmDetectionFilterTest : public testing::Test { MockNeuralModel* model_; NeuralStylusPalmDetectionFilterModelConfig model_config_; std::unique_ptr<PalmDetectionFilter> palm_detection_filter_; + base::TimeDelta sample_period_ = base::Milliseconds(8); }; -class NeuralStylusPalmDetectionFilterDeathTest - : public NeuralStylusPalmDetectionFilterTest {}; +INSTANTIATE_TEST_SUITE_P(ParametricFilterTest, + NeuralStylusPalmDetectionFilterTest, + ::testing::Values(0, 8.0), + [](const auto& paramInfo) { + return paramInfo.param != 0.0 ? "ResamplingEnabled" + : "ResamplingDisabled"; + }); #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) -TEST_F(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) { +TEST_P(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) { EventDeviceInfo devinfo; std::vector<std::pair<DeviceCapabilities, bool>> devices = { {kNocturneTouchScreen, true}, @@ -123,24 +135,26 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) { } } -TEST_F(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) { +TEST(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) { EventDeviceInfo bad_devinfo; EXPECT_TRUE(CapabilitiesToDeviceInfo(kNocturneStylus, &bad_devinfo)); + std::unique_ptr<NeuralStylusPalmDetectionFilterModel> model_( + new testing::StrictMock<MockNeuralModel>); + std::unique_ptr<SharedPalmDetectionFilterState> shared_palm_state = + std::make_unique<SharedPalmDetectionFilterState>(); EXPECT_DCHECK_DEATH({ - NeuralStylusPalmDetectionFilter f( - bad_devinfo, - std::unique_ptr<NeuralStylusPalmDetectionFilterModel>(model_), - shared_palm_state.get()); + NeuralStylusPalmDetectionFilter f(bad_devinfo, std::move(model_), + shared_palm_state.get()); }); } #endif -TEST_F(NeuralStylusPalmDetectionFilterTest, NameTest) { +TEST_P(NeuralStylusPalmDetectionFilterTest, NameTest) { EXPECT_EQ("NeuralStylusPalmDetectionFilter", palm_detection_filter_->FilterNameForTesting()); } -TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) { +TEST_P(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled, expected_cancelled; touch_[0].touching = true; @@ -167,7 +181,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) { EXPECT_EQ(expected_cancelled, actual_cancelled); } -TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) { +TEST_P(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; touch_[0].touching = true; touch_[0].tracking_id = 600; @@ -185,7 +199,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) { touch_[0].was_touching = true; touch_[0].touching = false; touch_[0].tracking_id = -1; - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); EXPECT_TRUE(actual_held.none()); @@ -203,7 +217,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) { touch_[0].was_touching = true; touch_[0].touching = false; touch_[0].tracking_id = -1; - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); EXPECT_TRUE(actual_held.none()); @@ -212,7 +226,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) { EXPECT_TRUE(actual_cancelled.none()); } -TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) { +TEST_P(NeuralStylusPalmDetectionFilterTest, CallFilterTest) { // Set up 3 touches as touching: // Touch 0 starts off and is sent twice // Touch 1 and 2 are then added on: 2 is far away, 1 is nearby. We expect a @@ -259,7 +273,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) { touch_[2].tracking_id = 502; touch_[2].slot = 2; - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); EXPECT_TRUE(actual_held.none()); @@ -284,7 +298,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) { Inference(testing::Pointwise(testing::FloatEq(), features))) .Times(1) .WillOnce(testing::Return(0.5)); - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); EXPECT_TRUE(actual_held.none()); @@ -306,7 +320,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) { Inference(testing::Pointwise(testing::FloatEq(), features))) .Times(1) .WillOnce(testing::Return(0.5)); - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); EXPECT_TRUE(actual_held.none()); @@ -314,7 +328,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTest) { EXPECT_EQ(actual_cancelled, expected_cancelled); } -TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) { +TEST_P(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled; @@ -369,7 +383,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) { Inference(testing::Pointwise(testing::FloatEq(), features))) .Times(1) .WillOnce(testing::Return(0.5)); - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); // Slot 0 is held. @@ -425,7 +439,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) { .Times(1) .WillOnce(testing::Return(0.5)); - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); @@ -464,7 +478,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) { Inference(testing::Pointwise(testing::FloatEq(), features))) .Times(1) .WillOnce(testing::Return(0.5)); - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); @@ -483,7 +497,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, CallFilterTestWithAdaptiveHold) { EXPECT_EQ(actual_cancelled, expected_cancelled); } -TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) { +TEST_P(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; base::TimeTicks touch_time = base::TimeTicks::UnixEpoch() + base::Milliseconds(10.0); @@ -501,7 +515,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) { if (i != 0) { touch_[0].was_touching = true; } - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); ASSERT_TRUE(actual_held.none()) << " Failed at " << i; @@ -509,7 +523,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) { } } -TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) { +TEST_P(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; std::bitset<kNumTouchEvdevSlots> expected_cancelled; base::TimeTicks touch_time = @@ -533,7 +547,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) { if (i != 0) { touch_[0].was_touching = true; } - touch_time += base::Milliseconds(8.0f); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); ASSERT_EQ(original_finger_time, @@ -553,7 +567,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) { } } -TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) { +TEST_P(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled; model_config_.heuristic_delay_start_if_palm = true; @@ -573,7 +587,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) { EXPECT_EQ(expected_cancelled, actual_cancelled); } -TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) { +TEST_P(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) { std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled; model_config_.heuristic_delay_start_if_palm = true; @@ -596,7 +610,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) { // Delay continues even afterwards, until inference time: then it's off. for (uint32_t i = 1; i < model_config_.max_sample_count - 1; ++i) { touch_[0].was_touching = true; - touch_time += base::Milliseconds(10.0); + touch_time += sample_period_; touch_[0].major = 15; touch_[0].minor = 15; touch_[0].x += 1; @@ -610,7 +624,7 @@ TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) { EXPECT_CALL(*model_, Inference(testing::_)) .Times(1) .WillOnce(testing::Return(-0.1)); - touch_time = base::TimeTicks::UnixEpoch() + base::Milliseconds(10.0); + touch_time += sample_period_; palm_detection_filter_->Filter(touch_, touch_time, &actual_held, &actual_cancelled); expected_held.set(0, false); diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc index d1813ef..7c581dd 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.cc @@ -4,6 +4,7 @@ #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h" +#include <base/logging.h> #include <algorithm> namespace ui { @@ -80,7 +81,7 @@ const static auto kPreferInitialEventDelay = * not interpolated, the values are taken from the 'after' sample unless the * requested time is very close to the 'before' sample. */ -PalmFilterSample getSampleAtTime(base::TimeTicks time, +PalmFilterSample GetSampleAtTime(base::TimeTicks time, const PalmFilterSample& before, const PalmFilterSample& after) { // Use the newest sample as the base, except when the requested time is very @@ -153,16 +154,30 @@ PalmFilterStroke::~PalmFilterStroke() {} void PalmFilterStroke::ProcessSample(const PalmFilterSample& sample) { DCHECK_EQ(tracking_id_, sample.tracking_id); - if (resample_period_.has_value()) { - Resample(sample); - return; + if (samples_seen_ == 0) { + first_sample_time_ = sample.time; } AddSample(sample); - while (samples_.size() > max_sample_count_) { - AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin()); - samples_.pop_front(); + if (resample_period_.has_value()) { + // Prune based on time + const base::TimeDelta max_duration = + (*resample_period_) * (max_sample_count_ - 1); + while (samples_.size() > 2 && + samples_.back().time - samples_[1].time >= max_duration) { + // We can only discard the sample if after it's discarded, we still cover + // the entire range. If we don't, we need to keep this sample for + // calculating resampled values. + AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin()); + samples_.pop_front(); + } + } else { + // Prune based on number of samples + while (samples_.size() > max_sample_count_) { + AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin()); + samples_.pop_front(); + } } } @@ -172,36 +187,6 @@ void PalmFilterStroke::AddSample(const PalmFilterSample& sample) { samples_seen_++; } -/** - * When resampling is enabled, we don't store all samples. Only the resampled - * values are stored into samples_. In addition, the last real event is stored - * into last_sample_, which is used to calculate the resampled values. - */ -void PalmFilterStroke::Resample(const PalmFilterSample& sample) { - if (samples_seen_ == 0) { - AddSample(sample); - last_sample_ = sample; - return; - } - - // We already have a valid last sample here. - DCHECK_LE(last_sample_.time, sample.time); - // Generate resampled values - base::TimeTicks next_sample_time = samples_.back().time + *resample_period_; - while (next_sample_time <= sample.time) { - AddSample(getSampleAtTime(next_sample_time, last_sample_, sample)); - next_sample_time = samples_.back().time + (*resample_period_); - } - last_sample_ = sample; - - // Prune the resampled collection - while ((samples_.back().time - samples_.front().time) >= - (*resample_period_) * max_sample_count_) { - AddToUnscaledCentroid(-samples_.front().point.OffsetFromOrigin()); - samples_.pop_front(); - } -} - void PalmFilterStroke::AddToUnscaledCentroid(const gfx::Vector2dF point) { const gfx::Vector2dF corrected_point = point - unscaled_centroid_sum_error_; const gfx::PointF new_unscaled_centroid = @@ -226,6 +211,47 @@ int PalmFilterStroke::tracking_id() const { return tracking_id_; } +base::TimeDelta PalmFilterStroke::Duration() const { + if (samples_.empty()) { + LOG(DFATAL) << "No samples available"; + return base::Milliseconds(0); + } + return samples_.back().time - first_sample_time_; +} + +base::TimeDelta PalmFilterStroke::PreviousDuration() const { + if (samples_.size() <= 1) { + LOG(DFATAL) << "Not enough samples"; + return base::Milliseconds(0); + } + const PalmFilterSample& secondToLastSample = samples_.rbegin()[1]; + return secondToLastSample.time - first_sample_time_; +} + +bool PalmFilterStroke::LastSampleCrossed(base::TimeDelta duration) const { + if (samples_.size() <= 1) { + // If there's only 1 sample, stroke just started and Duration() is zero. + return false; + } + return PreviousDuration() < duration && duration <= Duration(); +} + +PalmFilterSample PalmFilterStroke::GetSampleAt(base::TimeTicks time) const { + size_t i = 0; + for (; i < samples_.size() && samples_[i].time < time; ++i) { + } + + if (i < samples_.size() && !samples_.empty() && samples_[i].time == time) { + return samples_[i]; + } + if (i == 0 || i == samples_.size()) { + LOG(DFATAL) << "Invalid index: " << i + << ", can't interpolate for time: " << time; + return {}; + } + return GetSampleAtTime(time, samples_[i - 1], samples_[i]); +} + uint64_t PalmFilterStroke::samples_seen() const { return samples_seen_; } @@ -312,11 +338,10 @@ std::ostream& operator<<(std::ostream& out, const PalmFilterStroke& stroke) { out << " max_sample_count_ = " << stroke.max_sample_count_ << "\n"; if (stroke.resample_period_) { out << " resample_period_ = " << *(stroke.resample_period_) << "\n"; - out << " last_sample_ = " << stroke.last_sample_ << "\n"; } else { out << " resample_period_ = <not set>\n"; - out << " last_sample_ = <not valid b/c resampling is off>\n"; } + out << " first_sample_time_ = " << stroke.first_sample_time_ << "\n"; out << " unscaled_centroid_ = " << stroke.unscaled_centroid_ << "\n"; out << " unscaled_centroid_sum_error_ = " << stroke.unscaled_centroid_sum_error_ << "\n"; diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h index 47f0c33..d50a32d 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util.h @@ -12,7 +12,7 @@ #if defined(__ANDROID__) || defined(__ANDROID_HOST__) #undef LOG_INFO #undef LOG_WARNING -#include <chrome_to_android_compatibility_test_support.h> +#include <chrome_to_android_compatibility.h> #endif #include "base/time/time.h" #if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) @@ -85,6 +85,25 @@ class COMPONENT_EXPORT(EVDEV) PalmFilterStroke { float BiggestSize() const; // If no elements in stroke, returns 0.0; float MaxMajorRadius() const; + /** + * Return the time duration of this stroke. + */ + base::TimeDelta Duration() const; + /** + * Provide a (potentially resampled) sample at the requested time. + * Only interpolation is allowed. + * The requested time must be within the window at which the gesture occurred. + */ + PalmFilterSample GetSampleAt(base::TimeTicks time) const; + + /** + * Return true if the provided duration is between the duration of the + * previous sample and the current sample. In other words, if the addition of + * the last sample caused the total stroke duration to exceed the provided + * duration. Return false otherwise. + */ + bool LastSampleCrossed(base::TimeDelta duration) const; + const std::deque<PalmFilterSample>& samples() const; uint64_t samples_seen() const; int tracking_id() const; @@ -92,10 +111,8 @@ class COMPONENT_EXPORT(EVDEV) PalmFilterStroke { private: void AddToUnscaledCentroid(const gfx::Vector2dF point); void AddSample(const PalmFilterSample& sample); - /** - * Process the sample. Potentially store the resampled sample into samples_. - */ - void Resample(const PalmFilterSample& sample); + + base::TimeDelta PreviousDuration() const; std::deque<PalmFilterSample> samples_; const int tracking_id_; @@ -108,13 +125,9 @@ class COMPONENT_EXPORT(EVDEV) PalmFilterStroke { * number of times 'AddSample' has been called. */ uint64_t samples_seen_ = 0; - /** - * The last sample seen by the model. Used when resampling is enabled in order - * to compute the resampled value. - */ - PalmFilterSample last_sample_; const uint64_t max_sample_count_; + base::TimeTicks first_sample_time_; const base::Optional<base::TimeDelta> resample_period_; gfx::PointF unscaled_centroid_ = gfx::PointF(0., 0.); diff --git a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc index ac3c841..e7019b0 100644 --- a/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc +++ b/ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter_util_unittest.cc @@ -13,7 +13,7 @@ #include "testing/gtest/include/gtest/gtest.h" #if defined(__ANDROID__) || defined(__ANDROID_HOST__) #include <linux/input-event-codes.h> -#include "chrome_to_android_compatibility_test_support.h" +#include "chrome_to_android_compatibility.h" #else #include "ui/events/ozone/evdev/event_device_test_util.h" #endif @@ -393,23 +393,24 @@ TEST(PalmFilterStrokeTest, NumberOfResampledValues) { ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time))); ASSERT_EQ(1u, stroke.samples_seen()); - // Add second sample at time = T + 2ms. It's not yet time for the new frame, - // so no new sample should be generated. + // Add second sample at time = T + 4ms. All samples are stored, even if it's + // before the next resample time. base::TimeTicks time = down_time + base::Milliseconds(4); sample = CreatePalmFilterSample(touch_, time, model_config_, device_info); stroke.ProcessSample(sample); - ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time))); - ASSERT_EQ(1u, stroke.samples_seen()); + ASSERT_THAT(stroke.samples(), + ElementsAre(SampleTime(down_time), SampleTime(time))); + ASSERT_EQ(2u, stroke.samples_seen()); - // Add third sample at time = T + 10ms. An event at time = T + 8ms should be - // generated. + // Add third sample at time = T + 10ms. time = down_time + base::Milliseconds(10); sample = CreatePalmFilterSample(touch_, time, model_config_, device_info); stroke.ProcessSample(sample); ASSERT_THAT(stroke.samples(), ElementsAre(SampleTime(down_time), - SampleTime(down_time + base::Milliseconds(8)))); - ASSERT_EQ(2u, stroke.samples_seen()); + SampleTime(down_time + base::Milliseconds(4)), + SampleTime(down_time + base::Milliseconds(10)))); + ASSERT_EQ(3u, stroke.samples_seen()); } TEST(PalmFilterStrokeTest, ResamplingTest) { @@ -446,7 +447,7 @@ TEST(PalmFilterStrokeTest, ResamplingTest) { CreatePalmFilterSample(touch_, time, model_config_, device_info); stroke.ProcessSample(sample2); // The samples should remain unchanged - ASSERT_THAT(stroke.samples(), ElementsAre(sample1)); + ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2)); // Add third sample at time = T + 12ms. A resampled event at time = T + 8ms // should be generated. @@ -458,16 +459,16 @@ TEST(PalmFilterStrokeTest, ResamplingTest) { PalmFilterSample sample3 = CreatePalmFilterSample(touch_, time, model_config_, device_info); stroke.ProcessSample(sample3); - ASSERT_THAT( - stroke.samples(), - ElementsAre(sample1, SampleTime(down_time + base::Milliseconds(8)))); - - EXPECT_EQ(150, stroke.samples().back().point.x()); - EXPECT_EQ(22, stroke.samples().back().point.y()); - EXPECT_EQ(14, stroke.samples().back().major_radius); - EXPECT_EQ(13, stroke.samples().back().minor_radius); + ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2, sample3)); } +/** + * There should always be at least (max_sample_count - 1) * resample_period + * worth of samples. However, that's not sufficient. In the cases where the gap + * between samples is very large (larger than the sample horizon), there needs + * to be another sample in order to calculate resampled values throughout the + * entire range. + */ TEST(PalmFilterStrokeTest, MultipleResampledValues) { NeuralStylusPalmDetectionFilterModelConfig model_config_; model_config_.max_sample_count = 3; @@ -491,8 +492,7 @@ TEST(PalmFilterStrokeTest, MultipleResampledValues) { // First sample should go in as is ASSERT_THAT(stroke.samples(), ElementsAre(sample1)); - // Add second sample at time = T + 20ms. Two resampled values should be - // generated: 1) at time = T+8ms 2) at time = T+16ms + // Add second sample at time = T + 20ms. base::TimeTicks time = down_time + base::Milliseconds(20); touch_.x = 20; touch_.y = 30; @@ -501,22 +501,23 @@ TEST(PalmFilterStrokeTest, MultipleResampledValues) { PalmFilterSample sample2 = CreatePalmFilterSample(touch_, time, model_config_, device_info); stroke.ProcessSample(sample2); - ASSERT_THAT(stroke.samples(), - ElementsAre(SampleTime(down_time), - SampleTime(down_time + base::Milliseconds(8)), - SampleTime(down_time + base::Milliseconds(16)))); - - // First sample : time = T + 8ms - EXPECT_EQ(8, stroke.samples()[1].point.x()); - EXPECT_EQ(18, stroke.samples()[1].point.y()); - EXPECT_EQ(220, stroke.samples()[1].major_radius); - EXPECT_EQ(120, stroke.samples()[1].minor_radius); - - // Second sample : time = T + 16ms - EXPECT_EQ(16, stroke.samples().back().point.x()); - EXPECT_EQ(26, stroke.samples().back().point.y()); - EXPECT_EQ(220, stroke.samples().back().major_radius); - EXPECT_EQ(120, stroke.samples().back().minor_radius); + + ASSERT_THAT(stroke.samples(), ElementsAre(sample1, sample2)); + + // Verify resampled sample : time = T + 8ms + PalmFilterSample resampled_sample = + stroke.GetSampleAt(down_time + base::Milliseconds(8)); + EXPECT_EQ(8, resampled_sample.point.x()); + EXPECT_EQ(18, resampled_sample.point.y()); + EXPECT_EQ(220, resampled_sample.major_radius); + EXPECT_EQ(120, resampled_sample.minor_radius); + + // Verify resampled sample : time = T + 16ms + resampled_sample = stroke.GetSampleAt(down_time + base::Milliseconds(16)); + EXPECT_EQ(16, resampled_sample.point.x()); + EXPECT_EQ(26, resampled_sample.point.y()); + EXPECT_EQ(220, resampled_sample.major_radius); + EXPECT_EQ(120, resampled_sample.minor_radius); } } // namespace ui |