blob: d2954a158ab40fa833da1b6dc225515839ed642b [file] [log] [blame]
// 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