Commit 690ad615 authored by Rob Schonberger's avatar Rob Schonberger Committed by Commit Bot

Add PalmDetectionFilter and 2 initial implemenations.

This adds a palm detection filter that interacts with the concept of
"held" items. The filters will be used in TouchEventConverterEvdev to
either hold or cancel touches, in addition to the behavior from device
firmware.

The two implementations are:
1. An always "open" detection, that doesn't mark anything to hold/cancel.
2. A simple heuristic that is based on interaction with stylus touches.

Both implementations have unit tests that cover 100% of the code.

Identical to
https://chromium-review.googlesource.com/c/chromium/src/+/1708148
except uses an EXPECT_DCHECK_DEATH instead of EXPECT_DEATH since that
fails (obviously) sometimes. Revert of revert
https://chromium-review.googlesource.com/c/chromium/src/+/1716389 .

Bug: 982118
Change-Id: I3712b905f49218149082c352b939e7b9b6b23c29
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1715287
Auto-Submit: Rob Schonberger <robsc@chromium.org>
Reviewed-by: default avatarMichael Spang <spang@chromium.org>
Commit-Queue: Rob Schonberger <robsc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#680620}
parent 93876a37
......@@ -618,6 +618,8 @@ if (!is_ios) {
"ozone/evdev/testing/fake_cursor_delegate_evdev.h",
"ozone/evdev/touch_event_converter_evdev_unittest.cc",
"ozone/evdev/touch_filter/false_touch_finder_unittest.cc",
"ozone/evdev/touch_filter/heuristic_stylus_palm_detection_filter_unittest.cc",
"ozone/evdev/touch_filter/open_palm_detection_filter_unittest.cc",
"ozone/gamepad/generic_gamepad_mapping_unittest.cc",
]
......
......@@ -116,10 +116,17 @@ if (use_ozone) {
"evdev/touch_filter/false_touch_finder.h",
"evdev/touch_filter/far_apart_taps_touch_noise_filter.cc",
"evdev/touch_filter/far_apart_taps_touch_noise_filter.h",
"evdev/touch_filter/heuristic_stylus_palm_detection_filter.cc",
"evdev/touch_filter/heuristic_stylus_palm_detection_filter.h",
"evdev/touch_filter/horizontally_aligned_touch_noise_filter.cc",
"evdev/touch_filter/horizontally_aligned_touch_noise_filter.h",
"evdev/touch_filter/low_pressure_filter.cc",
"evdev/touch_filter/low_pressure_filter.h",
"evdev/touch_filter/open_palm_detection_filter.cc",
"evdev/touch_filter/open_palm_detection_filter.h",
"evdev/touch_filter/palm_detection_filter.cc",
"evdev/touch_filter/palm_detection_filter.h",
"evdev/touch_filter/shared_palm_detection_filter_state.h",
"evdev/touch_filter/single_position_touch_noise_filter.cc",
"evdev/touch_filter/single_position_touch_noise_filter.h",
"evdev/touch_filter/touch_filter.h",
......
// 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/heuristic_stylus_palm_detection_filter.h"
#include <linux/input.h>
namespace ui {
void HeuristicStylusPalmDetectionFilter::Filter(
const std::vector<InProgressTouchEvdev>& touches,
base::TimeTicks time,
std::bitset<kNumTouchEvdevSlots>* slots_to_hold,
std::bitset<kNumTouchEvdevSlots>* slots_to_suppress) {
slots_to_hold->reset();
slots_to_suppress->reset();
base::TimeTicks latest_stylus_time =
shared_palm_state_->latest_stylus_touch_time_;
for (int i = 0; i < kNumTouchEvdevSlots; ++i) {
const auto& touch = touches[i];
if (touch.tool_code == BTN_TOOL_PEN) {
// We detect BTN_TOOL_PEN whenever a pen is even hovering. This is
// mutually exclusive with finger touches, which is what we're interested
// in. So we update latest_time.
shared_palm_state_->latest_stylus_touch_time_ = time;
return;
}
if (!touch.touching) {
stroke_length_[i] = 0;
continue;
}
if (stroke_length_[i] == 0) {
// new touch!
touch_started_time_[i] = time;
}
stroke_length_[i]++;
base::TimeDelta time_since_stylus_for_touch_start =
touch_started_time_[i] - latest_stylus_time;
if (time_since_stylus_for_touch_start < time_after_stylus_to_cancel_) {
slots_to_suppress->set(i, 1);
} else if (time_since_stylus_for_touch_start < time_after_stylus_to_hold_ &&
stroke_length_[i] <= hold_stroke_count_) {
slots_to_hold->set(i, 1);
}
}
}
HeuristicStylusPalmDetectionFilter::HeuristicStylusPalmDetectionFilter(
SharedPalmDetectionFilterState* shared_palm_state,
int hold_stroke_count,
base::TimeDelta hold,
base::TimeDelta cancel)
: PalmDetectionFilter(shared_palm_state),
hold_stroke_count_(hold_stroke_count),
time_after_stylus_to_hold_(hold),
time_after_stylus_to_cancel_(cancel) {
touch_started_time_.resize(kNumTouchEvdevSlots, base::TimeTicks::UnixEpoch());
stroke_length_.resize(kNumTouchEvdevSlots, 0);
DCHECK(hold >= cancel) << "Expected hold time to be longer than cancel time.";
}
HeuristicStylusPalmDetectionFilter::~HeuristicStylusPalmDetectionFilter() {}
} // namespace ui
// 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.
#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_HEURISTIC_STYLUS_PALM_DETECTION_FILTER_H_
#define UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_HEURISTIC_STYLUS_PALM_DETECTION_FILTER_H_
#include <bitset>
#include <vector>
#include "base/time/time.h"
#include "ui/events/ozone/evdev/touch_evdev_types.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 {
// A heuristic implementation of PalmDetectionFilter.
// Relies on firmware palm detection, but modifies behavior _after_ a stylus
// touch since our mutual-exclusion of stylus/touch means that we do not trust
// the device right after stylus.
// Configured with 3 inputs:
// 1. How many strokes to hold on to when holding.
// 2. TimeDelta for cancellation: any strokes started within this delta are
// cancelled automatically.
// 3. TimeDelta for hold: any strokes started after the cancellation and before
// this are held for the stroke count (as above). If they're cancelled
// externally, we never report them. If they terminate before the count, we
// output all items.
class EVENTS_OZONE_EVDEV_EXPORT HeuristicStylusPalmDetectionFilter
: public PalmDetectionFilter {
public:
HeuristicStylusPalmDetectionFilter(
SharedPalmDetectionFilterState* shared_palm_state,
int hold_stroke_count,
base::TimeDelta hold,
base::TimeDelta cancel);
~HeuristicStylusPalmDetectionFilter() override;
void Filter(const std::vector<InProgressTouchEvdev>& touches,
base::TimeTicks time,
std::bitset<kNumTouchEvdevSlots>* slots_to_hold,
std::bitset<kNumTouchEvdevSlots>* slots_to_suppress) override;
private:
const int hold_stroke_count_;
const base::TimeDelta time_after_stylus_to_hold_;
const base::TimeDelta time_after_stylus_to_cancel_;
std::vector<base::TimeTicks> touch_started_time_;
// How many items have we seen in this stroke so far?
std::vector<int> stroke_length_;
DISALLOW_COPY_AND_ASSIGN(HeuristicStylusPalmDetectionFilter);
};
} // namespace ui
#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_HEURISTIC_STYLUS_PALM_DETECTION_FILTER_H_
// 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/heuristic_stylus_palm_detection_filter.h"
#include <linux/input.h>
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.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 HeuristicStylusPalmDetectionFilterTest : public testing::Test {
public:
HeuristicStylusPalmDetectionFilterTest() = default;
void SetUp() override {
shared_palm_state.reset(new SharedPalmDetectionFilterState);
palm_detection_filter_.reset(new HeuristicStylusPalmDetectionFilter(
shared_palm_state.get(), hold_sample_count, hold_time, suppress_time));
touches_.resize(kNumTouchEvdevSlots);
test_start_time_ = base::TimeTicks::Now();
}
protected:
const int hold_sample_count = 5;
const base::TimeDelta hold_time = base::TimeDelta::FromSecondsD(1.0);
const base::TimeDelta suppress_time = base::TimeDelta::FromSecondsD(0.4);
const base::TimeDelta sample_interval =
base::TimeDelta::FromMillisecondsD(7.5);
std::unique_ptr<SharedPalmDetectionFilterState> shared_palm_state;
std::unique_ptr<PalmDetectionFilter> palm_detection_filter_;
std::vector<InProgressTouchEvdev> touches_;
base::TimeTicks test_start_time_;
DISALLOW_COPY_AND_ASSIGN(HeuristicStylusPalmDetectionFilterTest);
};
class HeuristicStylusPalmDetectionFilterDeathTest
: public HeuristicStylusPalmDetectionFilterTest {};
TEST_F(HeuristicStylusPalmDetectionFilterDeathTest, TestDCheck) {
// We run with a time where hold_time < suppress_time, which should DCHECK.
EXPECT_DCHECK_DEATH(
palm_detection_filter_.reset(new HeuristicStylusPalmDetectionFilter(
shared_palm_state.get(), hold_sample_count, hold_time,
hold_time + base::TimeDelta::FromMillisecondsD(0.1))));
}
TEST_F(HeuristicStylusPalmDetectionFilterTest, TestSetsToZero) {
std::bitset<kNumTouchEvdevSlots> suppress, hold;
suppress.set(kNumTouchEvdevSlots - 1, 1);
hold.set(0, 1);
palm_detection_filter_->Filter(touches_, test_start_time_, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
}
TEST_F(HeuristicStylusPalmDetectionFilterTest, TestCancelAfterStylus) {
touches_[0].touching = true;
touches_[0].tool_code = BTN_TOOL_PEN;
std::bitset<kNumTouchEvdevSlots> suppress, hold;
// Set Palm as test_start_time_;
palm_detection_filter_->Filter(touches_, test_start_time_, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
// Now, lets start two touches 7.5ms afterwards.
touches_[0].tool_code = 0;
touches_[1].touching = true;
base::TimeTicks start_time = test_start_time_ + sample_interval;
palm_detection_filter_->Filter(touches_, start_time, &hold, &suppress);
// expect none held, 0 and 1 cancelled, and others untouched.
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.test(0));
EXPECT_TRUE(suppress.test(1));
suppress.reset(0);
suppress.reset(1);
EXPECT_TRUE(suppress.none());
// Now, what if we keep going with these strokes for a long time.
for (;
start_time < test_start_time_ + base::TimeDelta::FromMillisecondsD(1000);
start_time += sample_interval) {
palm_detection_filter_->Filter(touches_, start_time, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.test(0));
EXPECT_TRUE(suppress.test(1));
suppress.reset(0);
suppress.reset(1);
EXPECT_TRUE(suppress.none());
}
}
TEST_F(HeuristicStylusPalmDetectionFilterTest, TestHoldAfterStylus) {
touches_[0].touching = true;
touches_[0].tool_code = BTN_TOOL_PEN;
std::bitset<kNumTouchEvdevSlots> suppress, hold;
// Set Palm as test_start_time_;
palm_detection_filter_->Filter(touches_, test_start_time_, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
// Now, lets start two touches a little before end of hold time.
touches_[0].tool_code = 0;
touches_[1].touching = true;
base::TimeTicks start_time =
test_start_time_ + hold_time - (hold_sample_count - 2) * sample_interval;
palm_detection_filter_->Filter(touches_, start_time, &hold, &suppress);
EXPECT_TRUE(suppress.none());
EXPECT_TRUE(hold.test(0));
EXPECT_TRUE(hold.test(1));
hold.reset(0);
hold.reset(1);
EXPECT_TRUE(hold.none());
for (int i = 0; i < 10; ++i) {
start_time += sample_interval;
palm_detection_filter_->Filter(touches_, start_time, &hold, &suppress);
EXPECT_TRUE(suppress.none());
// We've already held one item, so we expect 1 - the sample count to get
// held. Note that 1 of these falls _after_ the overall hold time, but we
// hold it since we depend on touch start.
if (i < hold_sample_count - 1) {
EXPECT_TRUE(hold.test(0)) << "Failed at i = " << i;
EXPECT_TRUE(hold.test(1)) << "Failed at i = " << i;
hold.reset(0);
hold.reset(1);
EXPECT_TRUE(hold.none());
} else {
EXPECT_TRUE(hold.none());
}
}
}
TEST_F(HeuristicStylusPalmDetectionFilterTest, TestNothingLongAfterStylus) {
touches_[0].touching = true;
touches_[0].tool_code = BTN_TOOL_PEN;
std::bitset<kNumTouchEvdevSlots> suppress, hold;
// Set Palm as test_start_time_;
palm_detection_filter_->Filter(touches_, test_start_time_, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
touches_[0].tool_code = 0;
touches_[1].touching = true;
base::TimeTicks start_time =
test_start_time_ + hold_time + base::TimeDelta::FromMillisecondsD(1e-2);
palm_detection_filter_->Filter(touches_, start_time, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
}
TEST_F(HeuristicStylusPalmDetectionFilterTest, TestHover) {
touches_[0].touching = false;
touches_[0].tool_code = BTN_TOOL_PEN;
std::bitset<kNumTouchEvdevSlots> suppress, hold;
// Set Palm as test_start_time_;
palm_detection_filter_->Filter(touches_, test_start_time_, &hold, &suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
// Now, do we filter a finger?
touches_[0].tool_code = 0;
touches_[0].touching = true;
base::TimeTicks start_time =
test_start_time_ + hold_time - base::TimeDelta::FromMillisecondsD(1e-2);
palm_detection_filter_->Filter(touches_, start_time, &hold, &suppress);
EXPECT_TRUE(hold.test(0));
EXPECT_TRUE(suppress.none());
hold.reset(0);
EXPECT_TRUE(hold.none());
}
} // namespace ui
// 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/open_palm_detection_filter.h"
namespace ui {
OpenPalmDetectionFilter::OpenPalmDetectionFilter(
SharedPalmDetectionFilterState* shared_palm_state)
: PalmDetectionFilter(shared_palm_state) {}
void OpenPalmDetectionFilter::Filter(
const std::vector<InProgressTouchEvdev>& touches,
base::TimeTicks time,
std::bitset<kNumTouchEvdevSlots>* slots_to_hold,
std::bitset<kNumTouchEvdevSlots>* slots_to_suppress) {
slots_to_hold->reset();
slots_to_suppress->reset();
}
OpenPalmDetectionFilter::~OpenPalmDetectionFilter() {}
} // namespace ui
// 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.
#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_OPEN_PALM_DETECTION_FILTER_H_
#define UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_OPEN_PALM_DETECTION_FILTER_H_
#include <bitset>
#include <vector>
#include "base/time/time.h"
#include "ui/events/ozone/evdev/touch_evdev_types.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 {
// A simple implementation of PalmDetectionFilter.
// Does not delay or set anything to palm.
class EVENTS_OZONE_EVDEV_EXPORT OpenPalmDetectionFilter
: public PalmDetectionFilter {
public:
explicit OpenPalmDetectionFilter(
SharedPalmDetectionFilterState* shared_palm_state);
~OpenPalmDetectionFilter() override;
void Filter(const std::vector<InProgressTouchEvdev>& touches,
base::TimeTicks time,
std::bitset<kNumTouchEvdevSlots>* slots_to_hold,
std::bitset<kNumTouchEvdevSlots>* slots_to_suppress) override;
private:
DISALLOW_COPY_AND_ASSIGN(OpenPalmDetectionFilter);
};
} // namespace ui
#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_OPEN_PALM_DETECTION_FILTER_H_
// 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/open_palm_detection_filter.h"
#include "testing/gtest/include/gtest/gtest.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 OpenPalmDetectionFilterTest : public testing::Test {
public:
OpenPalmDetectionFilterTest() = default;
void SetUp() override {
shared_palm_state.reset(new SharedPalmDetectionFilterState);
palm_detection_filter_.reset(
new OpenPalmDetectionFilter(shared_palm_state.get()));
}
protected:
std::unique_ptr<SharedPalmDetectionFilterState> shared_palm_state;
std::unique_ptr<PalmDetectionFilter> palm_detection_filter_;
DISALLOW_COPY_AND_ASSIGN(OpenPalmDetectionFilterTest);
};
TEST_F(OpenPalmDetectionFilterTest, TestSetsToZero) {
std::bitset<kNumTouchEvdevSlots> suppress, hold;
suppress.set(kNumTouchEvdevSlots - 1, 1);
hold.set(0, 1);
std::vector<InProgressTouchEvdev> inputs;
inputs.resize(kNumTouchEvdevSlots);
palm_detection_filter_->Filter(inputs, base::TimeTicks::Now(), &hold,
&suppress);
EXPECT_TRUE(hold.none());
EXPECT_TRUE(suppress.none());
}
} // namespace ui
// 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/palm_detection_filter.h"
namespace ui {
PalmDetectionFilter::PalmDetectionFilter(
SharedPalmDetectionFilterState* shared_palm_state)
: shared_palm_state_(shared_palm_state) {
DCHECK(shared_palm_state != nullptr);
}
PalmDetectionFilter::~PalmDetectionFilter() {}
} // namespace ui
// 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.
#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_PALM_DETECTION_FILTER_H_
#define UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_PALM_DETECTION_FILTER_H_
#include <bitset>
#include <vector>
#include "base/time/time.h"
#include "ui/events/ozone/evdev/touch_evdev_types.h"
#include "ui/events/ozone/evdev/touch_filter/shared_palm_detection_filter_state.h"
namespace ui {
// An abstract palm detection filter. It has two functions:
// 1. To decide which touches to "hold"
// 2. To decide which touches to suppress / "cancel".
// Interface is similar to that of TouchFilter but enshrines the "hold" as a
// first class citizen.
class EVENTS_OZONE_EVDEV_EXPORT PalmDetectionFilter {
public:
// shared_palm_state is not owned!
explicit PalmDetectionFilter(
SharedPalmDetectionFilterState* shared_palm_state);
// Execute a filter event. Expected to be executed on every update to touches.
// Arguments are:
// 1. touches: a vector of kNumTouchEvdevSlots touches.
// 2. time: the latest event time in touches.
// 3. slots_to_hold: output bitset of slots to hold. Must not be null.
// 4. slots_to_suppress: output bitset of slots to suppress/cancel. Must not
// be null.
//
// Note that if a slot is marked as both suppress and hold, we expect the
// suppress to override the hold.
virtual void Filter(const std::vector<InProgressTouchEvdev>& touches,
base::TimeTicks time,
std::bitset<kNumTouchEvdevSlots>* slots_to_hold,
std::bitset<kNumTouchEvdevSlots>* slots_to_suppress) = 0;
virtual ~PalmDetectionFilter();
protected:
// Not owned!
SharedPalmDetectionFilterState* shared_palm_state_;
};
} // namespace ui
#endif // UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_PALM_DETECTION_FILTER_H_
// 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.
#ifndef UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_SHARED_PALM_DETECTION_FILTER_STATE_H_
#define UI_EVENTS_OZONE_EVDEV_TOUCH_FILTER_SHARED_PALM_DETECTION_FILTER_STATE_H_
#include "base/time/time.h"
namespace ui {
struct SharedPalmDetectionFilterState {
// The latest stylus touch time. Note that this can include "hover".
base::TimeTicks latest_stylus_touch_time_ = base::TimeTicks::UnixEpoch();
};
} // namespace ui
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment