| // Copyright 2019 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 "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h" |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/test/gtest_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/events/ozone/evdev/event_device_test_util.h" |
| #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" |
| #include "ui/events/ozone/evdev/touch_filter/shared_palm_detection_filter_state.h" |
| |
| namespace ui { |
| |
| class MockNeuralModel : public NeuralStylusPalmDetectionFilterModel { |
| public: |
| MOCK_CONST_METHOD1(Inference, float(const std::vector<float>& features)); |
| MOCK_CONST_METHOD0(config, |
| const NeuralStylusPalmDetectionFilterModelConfig&()); |
| }; |
| |
| class NeuralStylusPalmDetectionFilterTest : public testing::Test { |
| public: |
| NeuralStylusPalmDetectionFilterTest() = default; |
| void SetUp() override { |
| shared_palm_state.reset(new SharedPalmDetectionFilterState); |
| model_ = new testing::StrictMock<MockNeuralModel>; |
| model_config_.biggest_near_neighbor_count = 2; |
| model_config_.min_sample_count = 2; |
| model_config_.max_sample_count = 5; |
| model_config_.max_neighbor_distance_in_mm = 20; |
| model_config_.heuristic_palm_touch_limit = 40; |
| model_config_.heuristic_palm_area_limit = 1000; |
| model_config_.max_dead_neighbor_time = |
| base::TimeDelta::FromMillisecondsD(100); |
| EXPECT_CALL(*model_, config()) |
| .Times(testing::AnyNumber()) |
| .WillRepeatedly(testing::ReturnRef(model_config_)); |
| EXPECT_TRUE( |
| CapabilitiesToDeviceInfo(kNocturneTouchScreen, &nocturne_touchscreen_)); |
| palm_detection_filter_.reset(new NeuralStylusPalmDetectionFilter( |
| nocturne_touchscreen_, |
| std::unique_ptr<NeuralStylusPalmDetectionFilterModel>(model_), |
| shared_palm_state.get())); |
| touch_.resize(kNumTouchEvdevSlots); |
| for (size_t i = 0; i < touch_.size(); ++i) { |
| touch_[i].slot = i; |
| } |
| } |
| |
| protected: |
| std::vector<InProgressTouchEvdev> touch_; |
| std::unique_ptr<SharedPalmDetectionFilterState> shared_palm_state; |
| EventDeviceInfo nocturne_touchscreen_; |
| // Owned by the filter. |
| MockNeuralModel* model_; |
| NeuralStylusPalmDetectionFilterModelConfig model_config_; |
| std::unique_ptr<PalmDetectionFilter> palm_detection_filter_; |
| DISALLOW_COPY_AND_ASSIGN(NeuralStylusPalmDetectionFilterTest); |
| }; |
| |
| class NeuralStylusPalmDetectionFilterDeathTest |
| : public NeuralStylusPalmDetectionFilterTest {}; |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, EventDeviceSimpleTest) { |
| EventDeviceInfo devinfo; |
| std::vector<std::pair<DeviceCapabilities, bool>> devices = { |
| {kNocturneTouchScreen, true}, |
| {kEveTouchScreen, true}, |
| {kLinkTouchscreen, true}, // No ABS_MT_TOOL_TYPE |
| {kNocturneStylus, false}, |
| {kKohakuTouchscreen, true}}; |
| for (const auto& it : devices) { |
| EXPECT_TRUE(CapabilitiesToDeviceInfo(it.first, &devinfo)); |
| EXPECT_EQ(it.second, |
| NeuralStylusPalmDetectionFilter:: |
| CompatibleWithNeuralStylusPalmDetectionFilter(devinfo)) |
| << "Failed on " << it.first.name; |
| } |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterDeathTest, EventDeviceConstructionDeath) { |
| EventDeviceInfo bad_devinfo; |
| EXPECT_TRUE(CapabilitiesToDeviceInfo(kNocturneStylus, &bad_devinfo)); |
| EXPECT_DCHECK_DEATH({ |
| NeuralStylusPalmDetectionFilter f( |
| bad_devinfo, |
| std::unique_ptr<NeuralStylusPalmDetectionFilterModel>(model_), |
| shared_palm_state.get()); |
| }); |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, NameTest) { |
| EXPECT_EQ("NeuralStylusPalmDetectionFilter", |
| palm_detection_filter_->FilterNameForTesting()); |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmAreaTest) { |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled, |
| expected_cancelled; |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 600; |
| touch_[0].x = 50; |
| touch_[0].y = 55; |
| touch_[0].major = 34; // 34 * 32 = 1088 |
| touch_[0].minor = 32; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.none()); |
| // end it |
| touch_[0].was_touching = true; |
| touch_[0].touching = false; |
| touch_[0].tracking_id = -1; |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| expected_cancelled.set(0, true); |
| EXPECT_EQ(expected_cancelled, actual_cancelled); |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, ShortTouchPalmSizeTest) { |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 600; |
| touch_[0].x = 50; |
| touch_[0].y = 55; |
| touch_[0].major = 25; |
| touch_[0].minor = 17; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.none()); |
| // end it |
| touch_[0].was_touching = true; |
| touch_[0].touching = false; |
| touch_[0].tracking_id = -1; |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.none()); |
| |
| touch_time += base::TimeDelta::FromSecondsD(3600); |
| touch_[0].touching = true; |
| touch_[0].major = 57; |
| touch_[0].tracking_id = 601; |
| touch_[0].was_touching = false; |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.none()); |
| touch_[0].was_touching = true; |
| touch_[0].touching = false; |
| touch_[0].tracking_id = -1; |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.test(0)); |
| actual_cancelled.set(0, false); |
| EXPECT_TRUE(actual_cancelled.none()); |
| } |
| |
| TEST_F(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 |
| // single call to filter, with only 1 neighbor! |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; |
| std::bitset<kNumTouchEvdevSlots> expected_cancelled; |
| |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 500; |
| touch_[0].major = 15; |
| touch_[0].minor = 10; |
| touch_[0].x = 15; |
| touch_[0].y = 10; |
| touch_[0].slot = 0; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.none()); |
| |
| // And now, let's add touches 1 and 2. |
| touch_[0].x = 17; |
| touch_[0].major = 14; |
| touch_[0].was_touching = true; |
| |
| touch_[1].touching = true; |
| touch_[1].major = 11; |
| touch_[1].minor = 9; |
| touch_[1].x = 30; |
| touch_[1].y = 25; |
| touch_[1].tracking_id = 501; |
| touch_[1].slot = 1; |
| |
| touch_[2].touching = true; |
| touch_[2].major = 10; |
| touch_[2].minor = 8; |
| touch_[2].x = 5500; |
| touch_[2].y = 2942; |
| touch_[2].tracking_id = 502; |
| touch_[2].slot = 2; |
| |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| EXPECT_TRUE(actual_cancelled.none()); |
| |
| touch_[3] = touch_[2]; |
| touch_[3].slot = 3; |
| touch_[3].x = 8000; |
| touch_[3].tracking_id = 504; |
| touch_[1].was_touching = true; |
| touch_[2].was_touching = true; |
| touch_[0].touching = false; |
| touch_[0].tracking_id = -1; |
| std::vector<float> features = { |
| 15, 10, 0, 0.25, 1, 14, 10, 0.05, 0.25, 1, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.4, 0.05, 0, 1, 0.512957, |
| 11, 9, 0, 0.625, 1, 11, 9, 0, 0.625, 1, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.4, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
| EXPECT_CALL(*model_, |
| Inference(testing::Pointwise(testing::FloatEq(), features))) |
| .Times(1) |
| .WillOnce(testing::Return(0.5)); |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_TRUE(actual_held.none()); |
| expected_cancelled.set(0, true); |
| EXPECT_EQ(actual_cancelled, expected_cancelled); |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOnceNotPalm) { |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 600; |
| touch_[0].x = 50; |
| touch_[0].y = 55; |
| touch_[0].major = 25; |
| touch_[0].minor = 17; |
| EXPECT_CALL(*model_, Inference(testing::_)) |
| .Times(1) |
| .WillOnce(testing::Return(-0.5)); |
| for (int i = 0; i < 5000; ++i) { |
| if (i != 0) { |
| touch_[0].was_touching = true; |
| } |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| ASSERT_TRUE(actual_held.none()) << " Failed at " << i; |
| ASSERT_TRUE(actual_cancelled.none()) << " Failed at " << i; |
| } |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, InferenceOncePalm) { |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; |
| std::bitset<kNumTouchEvdevSlots> expected_cancelled; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| expected_cancelled.set(0, true); |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 600; |
| touch_[0].x = 50; |
| touch_[0].y = 55; |
| touch_[0].major = 25; |
| touch_[0].minor = 17; |
| EXPECT_CALL(*model_, Inference(testing::_)) |
| .Times(1) |
| .WillOnce(testing::Return(0.5)); |
| |
| base::TimeTicks original_finger_time = |
| touch_time + base::TimeDelta::FromMillisecondsD(8.0f); |
| base::TimeTicks original_palm_time = |
| touch_time + |
| model_config_.max_sample_count * base::TimeDelta::FromMillisecondsD(8.0f); |
| |
| for (size_t i = 0; i < 5000; ++i) { |
| if (i != 0) { |
| touch_[0].was_touching = true; |
| } |
| touch_time += base::TimeDelta::FromMillisecondsD(8.0f); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| ASSERT_EQ(original_finger_time, |
| shared_palm_state->latest_finger_touch_time); |
| |
| ASSERT_TRUE(actual_held.none()) << " Failed at " << i; |
| if (i >= (model_config_.max_sample_count - 1)) { |
| ASSERT_EQ(expected_cancelled, actual_cancelled) << " Failed at " << i; |
| ASSERT_EQ(1u, shared_palm_state->active_palm_touches) |
| << " Failed at " << i; |
| ASSERT_EQ(original_palm_time, shared_palm_state->latest_palm_touch_time) |
| << " Failed at " << i; |
| } else { |
| ASSERT_EQ(1u, shared_palm_state->active_finger_touches) |
| << "Failed at " << i; |
| } |
| } |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortFingerTouch) { |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; |
| std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled; |
| model_config_.heuristic_delay_start_if_palm = true; |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 605; |
| touch_[0].x = 50; |
| touch_[0].y = 55; |
| // small touch! 39*21 = 819, which is < 1000. |
| touch_[0].major = 39; |
| touch_[0].minor = 21; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| |
| EXPECT_EQ(expected_held, actual_held); |
| EXPECT_EQ(expected_cancelled, actual_cancelled); |
| } |
| |
| TEST_F(NeuralStylusPalmDetectionFilterTest, DelayShortPalmTouch) { |
| std::bitset<kNumTouchEvdevSlots> actual_held, actual_cancelled; |
| std::bitset<kNumTouchEvdevSlots> expected_held, expected_cancelled; |
| model_config_.heuristic_delay_start_if_palm = true; |
| touch_[0].touching = true; |
| touch_[0].tracking_id = 605; |
| touch_[0].x = 50; |
| touch_[0].y = 55; |
| // big touch! 39*30 = 1170, which is > 1000. |
| touch_[0].major = 39; |
| touch_[0].minor = 30; |
| base::TimeTicks touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| |
| expected_held.set(0, true); |
| EXPECT_EQ(expected_held, actual_held); |
| EXPECT_EQ(expected_cancelled, actual_cancelled); |
| |
| // 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::TimeDelta::FromMillisecondsD(10.0); |
| touch_[0].major = 15; |
| touch_[0].minor = 15; |
| touch_[0].x += 1; |
| touch_[0].y += 1; |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| EXPECT_EQ(expected_held, actual_held) << " failed at " << i; |
| EXPECT_EQ(expected_cancelled, actual_cancelled) << " failed at " << i; |
| } |
| // When running inference, turn delay to false. |
| EXPECT_CALL(*model_, Inference(testing::_)) |
| .Times(1) |
| .WillOnce(testing::Return(-0.1)); |
| touch_time = |
| base::TimeTicks::UnixEpoch() + base::TimeDelta::FromMillisecondsD(10.0); |
| palm_detection_filter_->Filter(touch_, touch_time, &actual_held, |
| &actual_cancelled); |
| expected_held.set(0, false); |
| EXPECT_EQ(expected_held, actual_held); |
| EXPECT_EQ(expected_cancelled, actual_cancelled); |
| } |
| |
| } // namespace ui |