blob: a57773d15cac372bba32e6c7f411a0f57469afca [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <gtest/gtest.h>
#include "include/haptic_button_generator_filter_interpreter.h"
#include "include/unittest_util.h"
namespace gestures {
namespace {
class HapticButtonGeneratorFilterInterpreterTest : public ::testing::Test {};
class HapticButtonGeneratorFilterInterpreterTestInterpreter :
public Interpreter {
public:
HapticButtonGeneratorFilterInterpreterTestInterpreter()
: Interpreter(nullptr, nullptr, false) {}
virtual void SyncInterpret(HardwareState& hwstate, stime_t* timeout) {
if (return_value_.type != kGestureTypeNull)
ProduceGesture(return_value_);
}
virtual void HandleTimer(stime_t now, stime_t* timeout) {
ADD_FAILURE() << "HandleTimer on the next interpreter shouldn't be called";
}
Gesture return_value_;
};
struct GestureTestInputs {
stime_t time;
short touch_count; // -1 for timer callback
FingerState* fs;
Gesture gesture;
stime_t expected_button;
};
} // namespace {}
TEST(HapticButtonGeneratorFilterInterpreterTest, SimpleTest) {
HapticButtonGeneratorFilterInterpreterTestInterpreter* base_interpreter =
new HapticButtonGeneratorFilterInterpreterTestInterpreter;
HapticButtonGeneratorFilterInterpreter interpreter(
nullptr, base_interpreter, nullptr);
HardwareProperties hwprops = {
.right = 100, .bottom = 100,
.res_x = 10,
.res_y = 10,
.orientation_minimum = -1,
.orientation_maximum = 2,
.max_finger_cnt = 2, .max_touch_cnt = 5,
.supports_t5r2 = 0, .support_semi_mt = 0, .is_button_pad = 0,
.has_wheel = 0, .wheel_is_hi_res = 0,
.is_haptic_pad = 1,
};
TestInterpreterWrapper wrapper(&interpreter, &hwprops);
interpreter.enabled_.val_ = true;
FingerState fs[] = {
// TM, Tm, WM, Wm, pr, orient, x, y, id, flag
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 120, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 120, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 2, 0 },
{ 0, 0, 0, 0, 80, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 80, 0, 10, 1, 2, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 80, 0, 10, 1, 2, 0 },
};
HardwareState hs[] = {
// Expect to remove button press generated by firmare
make_hwstate(1.01, 0, 1, 1, &fs[0]),
make_hwstate(1.02, GESTURES_BUTTON_LEFT, 1, 1, &fs[1]),
make_hwstate(1.03, 0, 1, 1, &fs[2]),
// Expect to set button down when going above 'down force threshold' (130)
make_hwstate(2.01, 0, 1, 1, &fs[3]),
make_hwstate(2.03, 0, 1, 1, &fs[4]),
make_hwstate(2.05, 0, 1, 1, &fs[5]),
// Expect to set button up when going below 'up force threshold' (105)
make_hwstate(3.01, 0, 1, 1, &fs[6]),
make_hwstate(3.03, 0, 1, 1, &fs[7]),
make_hwstate(3.05, 0, 1, 1, &fs[8]),
// Expect not to set button down when no individual finger goes above the
// 'down force threshold' (130), even if multiple combined do
make_hwstate(4.01, 0, 2, 2, &fs[9]),
make_hwstate(4.03, 0, 2, 2, &fs[11]),
// Expect to set button down when one of multiple fingers goes above the
// 'down force threshold'
make_hwstate(4.05, 0, 2, 2, &fs[13]),
// Expect to set button up after all fingers leave
make_hwstate(5.01, 0, 0, 0, nullptr),
};
stime_t expected_buttons[] = {
0, 0, 0,
0, 0, GESTURES_BUTTON_LEFT,
GESTURES_BUTTON_LEFT, GESTURES_BUTTON_LEFT, 0,
0, 0,
GESTURES_BUTTON_LEFT,
0
};
for (size_t i = 0; i < arraysize(hs); i++) {
stime_t timeout = NO_DEADLINE;
wrapper.SyncInterpret(hs[i], &timeout);
EXPECT_EQ(hs[i].buttons_down, expected_buttons[i]);
}
}
TEST(HapticButtonGeneratorFilterInterpreterTest, NotHapticTest) {
HapticButtonGeneratorFilterInterpreterTestInterpreter* base_interpreter =
new HapticButtonGeneratorFilterInterpreterTestInterpreter;
HapticButtonGeneratorFilterInterpreter interpreter(
nullptr, base_interpreter, nullptr);
HardwareProperties hwprops = {
.right = 100, .bottom = 100,
.res_x = 10,
.res_y = 10,
.orientation_minimum = -1,
.orientation_maximum = 2,
.max_finger_cnt = 2, .max_touch_cnt = 5,
.supports_t5r2 = 0, .support_semi_mt = 0, .is_button_pad = 0,
.has_wheel = 0, .wheel_is_hi_res = 0,
.is_haptic_pad = 0,
};
TestInterpreterWrapper wrapper(&interpreter, &hwprops);
interpreter.enabled_.val_ = true;
FingerState fs[] = {
// TM, Tm, WM, Wm, pr, orient, x, y, id, flag
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 120, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, 0 },
};
HardwareState hs[] = {
// Expect to keep button press generated by firmware
make_hwstate(1.01, 0, 1, 1, &fs[0]),
make_hwstate(1.02, GESTURES_BUTTON_LEFT, 1, 1, &fs[1]),
make_hwstate(1.03, 0, 1, 1, &fs[2]),
// Expect to not generate a button pres
make_hwstate(2.01, 0, 1, 1, &fs[3]),
make_hwstate(2.03, 0, 1, 1, &fs[4]),
make_hwstate(2.05, 0, 1, 1, &fs[5]),
};
stime_t expected_buttons[] = {
0, GESTURES_BUTTON_LEFT, 0,
0, 0, 0
};
for (size_t i = 0; i < arraysize(hs); i++) {
stime_t timeout = NO_DEADLINE;
wrapper.SyncInterpret(hs[i], &timeout);
EXPECT_EQ(hs[i].buttons_down, expected_buttons[i]);
}
}
TEST(HapticButtonGeneratorFilterInterpreterTest, NotHapticConsumeGestureTest) {
HapticButtonGeneratorFilterInterpreter interpreter(
nullptr, nullptr, nullptr);
interpreter.is_haptic_pad_ = false;
interpreter.active_gesture_deadline_ = 0.0;
interpreter.release_suppress_factor_ = 0.0;
const Gesture kFling(kGestureFling, 0, 0, 20, 0, GESTURES_FLING_START);
const Gesture kMove(kGestureMove, 2, 3, 4.0, 5.0);
const Gesture kScroll(kGestureScroll, 0, 0, 20, 0);
// Verify no state change happens when a Fling is sent to haptics
// when this is not a haptics device
interpreter.active_gesture_ = false;
interpreter.ConsumeGesture(kFling);
EXPECT_FALSE(interpreter.active_gesture_);
EXPECT_EQ(interpreter.active_gesture_deadline_, 0.0);
EXPECT_EQ(interpreter.release_suppress_factor_, 0.0);
interpreter.active_gesture_ = true;
interpreter.ConsumeGesture(kFling);
EXPECT_TRUE(interpreter.active_gesture_);
EXPECT_EQ(interpreter.active_gesture_deadline_, 0.0);
EXPECT_EQ(interpreter.release_suppress_factor_, 0.0);
// Verify no state change happens when a MOVE is sent to haptics
// when this is not a haptics device
interpreter.active_gesture_ = false;
interpreter.ConsumeGesture(kMove);
EXPECT_FALSE(interpreter.active_gesture_);
EXPECT_EQ(interpreter.active_gesture_deadline_, 0.0);
EXPECT_EQ(interpreter.release_suppress_factor_, 0.0);
interpreter.active_gesture_ = true;
interpreter.ConsumeGesture(kMove);
EXPECT_TRUE(interpreter.active_gesture_);
EXPECT_EQ(interpreter.active_gesture_deadline_, 0.0);
EXPECT_EQ(interpreter.release_suppress_factor_, 0.0);
// Verify no state change happens when a Scroll is sent to haptics
// when this is not a haptics device
interpreter.active_gesture_ = false;
interpreter.ConsumeGesture(kScroll);
EXPECT_FALSE(interpreter.active_gesture_);
EXPECT_EQ(interpreter.active_gesture_deadline_, 0.0);
EXPECT_EQ(interpreter.release_suppress_factor_, 0.0);
interpreter.active_gesture_ = true;
interpreter.ConsumeGesture(kScroll);
EXPECT_TRUE(interpreter.active_gesture_);
EXPECT_EQ(interpreter.active_gesture_deadline_, 0.0);
EXPECT_EQ(interpreter.release_suppress_factor_, 0.0);
}
TEST(HapticButtonGeneratorFilterInterpreterTest,
GesturePreventsButtonDownTest) {
HapticButtonGeneratorFilterInterpreterTestInterpreter* base_interpreter =
new HapticButtonGeneratorFilterInterpreterTestInterpreter;
HapticButtonGeneratorFilterInterpreter interpreter(
nullptr, base_interpreter, nullptr);
HardwareProperties hwprops = {
.right = 100, .bottom = 100,
.res_x = 10,
.res_y = 10,
.orientation_minimum = -1,
.orientation_maximum = 2,
.max_finger_cnt = 2, .max_touch_cnt = 5,
.supports_t5r2 = 0, .support_semi_mt = 0, .is_button_pad = 0,
.has_wheel = 0, .wheel_is_hi_res = 0,
.is_haptic_pad = 1,
};
TestInterpreterWrapper wrapper(&interpreter, &hwprops);
interpreter.enabled_.val_ = true;
// TM, Tm, WM, Wm, pr, orient, x, y, id, flag
FingerState fs_low_force[] = {
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 50, 0, 10, 1, 2, 0 },
};
FingerState fs_high_force[] = {
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 2, 0 },
};
const Gesture kNull = Gesture();
const Gesture kScroll = Gesture(kGestureScroll, 0, 0, 20, 0);
const Gesture kFling = Gesture(kGestureFling, 0, 0, 20, 0,
GESTURES_FLING_START);
GestureTestInputs inputs[] = {
// Don't set the button down if a gesture is active.
{1.00, 2, fs_low_force, kScroll, GESTURES_BUTTON_NONE},
{1.01, 2, fs_high_force, kFling, GESTURES_BUTTON_NONE},
// If the button is down before a gesture starts, don't prevent the button
// from going back up.
{2.000, 2, fs_low_force, kNull, GESTURES_BUTTON_NONE},
{2.010, 2, fs_high_force, kNull, GESTURES_BUTTON_LEFT},
{2.030, 2, fs_high_force, kScroll, GESTURES_BUTTON_LEFT},
{2.040, 2, fs_low_force, kScroll, GESTURES_BUTTON_NONE},
{2.050, 2, fs_low_force, kFling, GESTURES_BUTTON_NONE},
// If there is no "ending" gesture event, allow button clicks after a short
// timeout.
{3.000, 2, fs_low_force, kScroll, GESTURES_BUTTON_NONE},
{3.010, 2, fs_high_force, kNull, GESTURES_BUTTON_NONE},
{3.011, 2, fs_high_force, kNull, GESTURES_BUTTON_NONE},
{3.011 + interpreter.active_gesture_timeout_, -1, nullptr, kNull, 0},
{3.200 + interpreter.active_gesture_timeout_,
2, fs_high_force, kNull, GESTURES_BUTTON_LEFT},
};
for (size_t i = 0; i < arraysize(inputs); i++) {
GestureTestInputs input = inputs[i];
base_interpreter->return_value_ = input.gesture;
stime_t timeout = NO_DEADLINE;
if (input.touch_count == -1) {
wrapper.HandleTimer(input.time, &timeout);
} else {
unsigned short touch_count =
static_cast<unsigned short>(input.touch_count);
HardwareState hs = make_hwstate(input.time, 0, touch_count, touch_count,
input.fs);
wrapper.SyncInterpret(hs, &timeout);
EXPECT_EQ(hs.buttons_down, input.expected_button);
}
}
}
TEST(HapticButtonGeneratorFilterInterpreterTest, DynamicThresholdTest) {
HapticButtonGeneratorFilterInterpreterTestInterpreter* base_interpreter =
new HapticButtonGeneratorFilterInterpreterTestInterpreter;
HapticButtonGeneratorFilterInterpreter interpreter(
nullptr, base_interpreter, nullptr);
HardwareProperties hwprops = {
.right = 100, .bottom = 100,
.res_x = 10,
.res_y = 10,
.orientation_minimum = -1,
.orientation_maximum = 2,
.max_finger_cnt = 2, .max_touch_cnt = 5,
.supports_t5r2 = 0, .support_semi_mt = 0, .is_button_pad = 0,
.has_wheel = 0, .wheel_is_hi_res = 0,
.is_haptic_pad = 1,
};
TestInterpreterWrapper wrapper(&interpreter, &hwprops);
interpreter.enabled_.val_ = true;
interpreter.use_dynamic_thresholds_.val_ = true;
FingerState fs[] = {
// TM, Tm, WM, Wm, pr, orient, x, y, id, flag
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 120, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 500, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 300, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 200, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 220, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 250, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 10, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 120, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 140, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 110, 0, 10, 1, 1, 0 },
{ 0, 0, 0, 0, 100, 0, 10, 1, 1, 0 },
};
std::pair<HardwareState, int> hs[] = {
// Expect to set button down when going above 'down force threshold' (130)
std::make_pair(make_hwstate(1.01, 0, 1, 1, &fs[0]), 0),
std::make_pair(make_hwstate(1.03, 0, 1, 1, &fs[1]), 0),
std::make_pair(make_hwstate(1.05, 0, 1, 1, &fs[2]), GESTURES_BUTTON_LEFT),
// Expect to increase button up threshold after seeing a very high force.
// Default 'up force threshold' is 105, but it increases to half of the max
// force seen.
std::make_pair(make_hwstate(2.01, 0, 1, 1, &fs[3]), GESTURES_BUTTON_LEFT),
std::make_pair(make_hwstate(2.03, 0, 1, 1, &fs[4]), GESTURES_BUTTON_LEFT),
std::make_pair(make_hwstate(2.05, 0, 1, 1, &fs[5]), 0),
// Expect to increase 'button down threshold' after seeing a very high
// force.
std::make_pair(make_hwstate(3.01, 0, 1, 1, &fs[6]), 0),
std::make_pair(make_hwstate(3.03, 0, 1, 1, &fs[7]), GESTURES_BUTTON_LEFT),
// Expect 'button down threshold' to return to normal (130) after seeing a
// low pressure value.
std::make_pair(make_hwstate(4.01, 0, 1, 1, &fs[8]), 0),
std::make_pair(make_hwstate(4.03, 0, 1, 1, &fs[9]), 0),
std::make_pair(make_hwstate(4.05, 0, 1, 1, &fs[10]), GESTURES_BUTTON_LEFT),
// Expect 'button up threshold' to return to normal after seeing a low
// pressure value.
std::make_pair(make_hwstate(5.01, 0, 1, 1, &fs[11]), GESTURES_BUTTON_LEFT),
std::make_pair(make_hwstate(5.03, 0, 1, 1, &fs[12]), 0),
};
for (size_t i = 0; i < arraysize(hs); i++) {
stime_t timeout = NO_DEADLINE;
wrapper.SyncInterpret(hs[i].first, &timeout);
EXPECT_EQ(hs[i].first.buttons_down, hs[i].second);
}
}
TEST(HapticButtonGeneratorFilterInterpreterTest, PalmTest) {
HapticButtonGeneratorFilterInterpreterTestInterpreter* base_interpreter =
new HapticButtonGeneratorFilterInterpreterTestInterpreter;
HapticButtonGeneratorFilterInterpreter interpreter(
nullptr, base_interpreter, nullptr);
HardwareProperties hwprops = {
.right = 100, .bottom = 100,
.res_x = 10,
.res_y = 10,
.orientation_minimum = -1,
.orientation_maximum = 2,
.max_finger_cnt = 2, .max_touch_cnt = 5,
.supports_t5r2 = 0, .support_semi_mt = 0, .is_button_pad = 0,
.has_wheel = 0, .wheel_is_hi_res = 0,
.is_haptic_pad = 1,
};
TestInterpreterWrapper wrapper(&interpreter, &hwprops);
interpreter.enabled_.val_ = true;
FingerState fs[] = {
// TM, Tm, WM, Wm, pr, orient, x, y, id, flag
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, GESTURES_FINGER_LARGE_PALM },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, GESTURES_FINGER_LARGE_PALM },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, GESTURES_FINGER_LARGE_PALM },
{ 0, 0, 0, 0, 50, 0, 10, 1, 1, GESTURES_FINGER_LARGE_PALM },
{ 0, 0, 0, 0, 50, 0, 10, 1, 2, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, GESTURES_FINGER_LARGE_PALM },
{ 0, 0, 0, 0, 80, 0, 10, 1, 2, 0 },
{ 0, 0, 0, 0, 160, 0, 10, 1, 1, GESTURES_FINGER_LARGE_PALM },
{ 0, 0, 0, 0, 160, 0, 10, 1, 2, 0 },
};
HardwareState hs[] = {
// Expect not to set button down when a lone palm goes above 'down force
// threshold'
make_hwstate(2.01, 0, 1, 1, &fs[0]),
make_hwstate(2.03, 0, 1, 1, &fs[1]),
make_hwstate(2.05, 0, 1, 1, &fs[2]),
// Expect not to set button down when there are multiple fingers and only a
// palm goes above 'down force threshold'
make_hwstate(4.01, 0, 2, 2, &fs[3]),
make_hwstate(4.03, 0, 2, 2, &fs[5]),
// Expect to set button down when there are multiple fingers and a non-palm
// goes above 'down force threshold'
make_hwstate(4.05, 0, 2, 2, &fs[7]),
};
stime_t expected_buttons[] = {
0, 0, 0,
0, 0,
GESTURES_BUTTON_LEFT,
};
for (size_t i = 0; i < arraysize(hs); i++) {
stime_t timeout = NO_DEADLINE;
wrapper.SyncInterpret(hs[i], &timeout);
EXPECT_EQ(hs[i].buttons_down, expected_buttons[i]);
}
}
} // namespace gestures