| // 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 |