/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "clar.h" #include "applib/graphics/gtypes.h" #include "kernel/events.h" #include "services/common/touch/touch.h" #include "services/common/touch/touch_event.h" #include "services/common/touch/touch_client.h" #include "util/size.h" #include <stddef.h> #include <stdint.h> #include <stdbool.h> #include "fake_events.h" // Stubs #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" bool gpoint_equal(const GPoint * const point_a, const GPoint * const point_b) { return (point_a->x == point_b->x && point_a->y == point_b->y); } void kernel_free(void *p) { } extern TouchEvent *touch_event_queue_get_event(TouchIdx touch_idx, uint32_t queue_idx); extern void touch_set_touch_state(TouchIdx touch_idx, TouchState touch_state, GPoint touch_down_pos, uint64_t touch_down_time_ms, TouchPressure touch_down_pressure); // setup and teardown void test_touch__initialize(void) { fake_event_init(); touch_reset(); } void test_touch__cleanup(void) { } void prv_test_touch_event(TouchEvent *touch_event, TouchIdx idx, TouchEventType type, GPoint *start_pos, uint64_t start_time_ms, TouchPressure start_pressure, GPoint *diff_pos, uint64_t diff_time_ms, TouchPressure diff_pressure, bool test_diff) { cl_assert(touch_event); cl_assert_equal_i(touch_event->type, type); cl_assert_equal_i(touch_event->index, idx); cl_assert_equal_i(touch_event->start_time_ms, start_time_ms); cl_assert(gpoint_equal(&touch_event->start_pos, start_pos)); cl_assert_equal_i(touch_event->start_pressure, start_pressure); if (type != TouchEvent_Touchdown) { cl_assert_equal_i(touch_event->diff_time_ms, diff_time_ms); cl_assert(gpoint_equal(&touch_event->diff_pos, diff_pos)); cl_assert_equal_i(touch_event->diff_pressure, diff_pressure); } } // tests void test_touch__handle_update_touchdown(void) { touch_handle_update(0, TouchState_FingerDown, &GPoint(15, 100), 3, 3686400); PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesAvailable); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_Touchdown, &GPoint(15, 100), 3686400, 3, &GPointZero, 0, 0, true); touch_event = touch_event_queue_get_event(1, 0); cl_assert_equal_p(touch_event, NULL); // Test second touch touch_handle_update(1, TouchState_FingerDown, &GPoint(1, 13), 5, 3686401); event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesAvailable); touch_event = touch_event_queue_get_event(1, 0); prv_test_touch_event(touch_event, 1, TouchEvent_Touchdown, &GPoint(1, 13), 3686401, 5, &GPointZero, 0, 0, true); } void test_touch__handle_update_liftoff(void) { // Test first touch touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerUp, &GPoint(15, 100), 0, 3686400); PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesAvailable); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_Liftoff, &GPointZero, 3686380, 0, &GPoint(15, 100), 20, 0, true); // Ensure nothing recorded for second touch touch_event = touch_event_queue_get_event(1, 0); cl_assert_equal_p(touch_event, NULL); // Test second touch touch_set_touch_state(1, TouchState_FingerDown, GPointZero, 0, 0); touch_handle_update(1, TouchState_FingerUp, &GPoint(1, 13), 0, 3686401); event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesAvailable); touch_event = touch_event_queue_get_event(1, 0); prv_test_touch_event(touch_event, 1, TouchEvent_Liftoff, &GPointZero, 0, 0, &GPoint(1, 13), 3686401, 0, true); } void test_touch__handle_update_liftoff_null_pos(void) { touch_handle_update(0, TouchState_FingerDown, &GPoint(1, 13), 5, 3686400); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_Touchdown, &GPoint(1, 13), 3686400, 5, &GPointZero, 0, 0, false); touch_handle_update(0, TouchState_FingerUp, NULL, 0, 3686410); touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_Liftoff, &GPoint(1, 13), 3686400, 5, &GPointZero, 10, -5, true); } void test_touch__handle_update_position(void) { touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(10, 10), 5, 3686400); PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesAvailable); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(10, 10), 20, 5, true); fake_event_reset_count(); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); cl_assert_equal_i(fake_event_get_count(), 0); // no event if previous one not handled touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(13, 13), 40, 6, true); } void test_touch__handle_update_position_stationary(void) { touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(10, 10), 5, 3686400); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(10, 10), 20, 5, true); // No touch event generated when finger remains stationary touch_handle_update(0, TouchState_FingerDown, &GPoint(10, 10), 5, 3686420); touch_event = touch_event_queue_get_event(0, 1); cl_assert_equal_p(touch_event, NULL); } void test_touch__handle_update_merge_position(void) { touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(10, 10), 5, 3686400); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(10, 10), 20, 5, true); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(13, 13), 40, 6, true); touch_handle_update(0, TouchState_FingerDown, &GPoint(18, 5), 1, 3686440); // Test the same event (event at index 1): it should update to reflect the difference between this // and the first event touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(18, 5), 60, 1, true); } void test_touch__handle_update_merge_liftoff(void) { touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(10, 10), 5, 3686400); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(10, 10), 20, 5, true); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(13, 13), 40, 6, true); touch_handle_update(0, TouchState_FingerUp, &GPoint(18, 5), 0, 3686440); // Test the same event (event at index 1): it should update to reflect the difference between this // and the first event and that it is a liftoff event touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_Liftoff, &GPointZero, 3686380, 0, &GPoint(18, 5), 60, 0, true); } void test_touch__handle_update_merge_liftoff_null_pos(void) { touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(10, 10), 5, 3686400); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(10, 10), 20, 5, true); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_PositionUpdate, &GPointZero, 3686380, 0, &GPoint(13, 13), 40, 6, true); touch_handle_update(0, TouchState_FingerUp, NULL, 0, 3686440); touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_Liftoff, &GPointZero, 3686380, 0, &GPoint(13, 13), 60, 0, true); } void test_touch__assert_null_pos_not_liftoff(void) { // NULL position not valid for touchdown event cl_assert_passert(touch_handle_update(0, TouchState_FingerDown, NULL, 5, 3686400)); touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 0, 0); // NULL position not valid for position update event cl_assert_passert(touch_handle_update(0, TouchState_FingerDown, NULL, 5, 3686400)); } void test_touch__handle_update_reset_queue_touchdown(void) { touch_set_touch_state(0, TouchState_FingerDown, GPointZero, 3686380, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_handle_update(0, TouchState_FingerUp, &GPoint(15, 100), 0, 3686400); TouchEvent *touch_event = touch_event_queue_get_event(0, 1); prv_test_touch_event(touch_event, 0, TouchEvent_Liftoff, &GPointZero, 3686380, 0, &GPoint(15, 100), 20, 0, true); // touchdown event should reset the touch event queue regardless of what is in it touch_handle_update(0, TouchState_FingerDown, &GPoint(31, 1), 6, 3686500); touch_event = touch_event_queue_get_event(0, 0); prv_test_touch_event(touch_event, 0, TouchEvent_Touchdown, &GPoint(31, 1), 3686500, 6, &GPointZero, 0, 0, true); touch_event = touch_event_queue_get_event(0, 1); cl_assert_equal_p(touch_event, NULL); } void test_touch__handle_update_pressure(void) { //TODO: We're not passing pressure updates to the UI yet (not so useful?) } typedef struct TouchEventContext { TouchEvent touch_events[4]; uint32_t idx; } TouchEventContext; static void prv_touch_event_dispatch_cb(const TouchEvent *event, void *context) { TouchEventContext *ctx = context; cl_assert(ctx->idx < ARRAY_LENGTH(ctx->touch_events)); ctx->touch_events[ctx->idx++] = *event; } void test_touch__dispatch_touch_events_single_finger(void) { TouchEventContext ctx = { .idx = 0 }; touch_dispatch_touch_events(0, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_handle_update(0, TouchState_FingerDown, &GPoint(15, 15), 6, 3686440); touch_dispatch_touch_events(0, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 2); prv_test_touch_event(&ctx.touch_events[0], 0, TouchEvent_Touchdown, &GPoint(13, 13), 3686420, 6, NULL, 0, 0, false); prv_test_touch_event(&ctx.touch_events[1], 0, TouchEvent_PositionUpdate, &GPoint(13, 13), 3686420, 6, &GPoint(2, 2), 20, 0, true); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); cl_assert_equal_p(touch_event, NULL); } void test_touch__dispatch_touch_events_two_fingers(void) { TouchEventContext ctx = { .idx = 0 }; touch_dispatch_touch_events(0, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 0); touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_handle_update(0, TouchState_FingerDown, &GPoint(15, 15), 6, 3686440); touch_handle_update(1, TouchState_FingerDown, &GPoint(55, 55), 2, 3686480); touch_handle_update(1, TouchState_FingerDown, &GPoint(33, 33), 7, 3686500); touch_dispatch_touch_events(0, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 2); prv_test_touch_event(&ctx.touch_events[0], 0, TouchEvent_Touchdown, &GPoint(13, 13), 3686420, 6, NULL, 0, 0, false); prv_test_touch_event(&ctx.touch_events[1], 0, TouchEvent_PositionUpdate, &GPoint(13, 13), 3686420, 6, &GPoint(2, 2), 20, 0, true); touch_dispatch_touch_events(1, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 4); prv_test_touch_event(&ctx.touch_events[2], 1, TouchEvent_Touchdown, &GPoint(55, 55), 3686480, 2, NULL, 0, 0, false); prv_test_touch_event(&ctx.touch_events[3], 1, TouchEvent_PositionUpdate, &GPoint(55, 55), 3686480, 2, &GPoint(-22, -22), 20, 5, true); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); cl_assert_equal_p(touch_event, NULL); } void test_touch__cancel_touches(void) { touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_handle_update(0, TouchState_FingerDown, &GPoint(15, 15), 6, 3686440); touch_handle_update(1, TouchState_FingerDown, &GPoint(55, 55), 2, 3686480); touch_handle_update(1, TouchState_FingerDown, &GPoint(33, 33), 7, 3686500); touch_handle_driver_event(TouchDriverEvent_ControllerError); PebbleEvent event = fake_event_get_last(); // Touches cancelled event generated cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesCancelled); // no more touches TouchEvent *touch_event = touch_event_queue_get_event(0, 0); cl_assert_equal_p(touch_event, NULL); touch_event = touch_event_queue_get_event(1, 0); cl_assert_equal_p(touch_event, NULL); } // test that the first dispatch after a cancel event is pended does not return any touches, even // if new touches have arrived - this is to ensure that the valid new touches are not cancelled // by the cancellation event if it is pended before previous touches void test_touch__cancel_touches_handle_first_dispatch(void) { touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_handle_driver_event(TouchDriverEvent_ControllerError); PebbleEvent event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesCancelled); touch_handle_update(0, TouchState_FingerDown, &GPoint(15, 15), 6, 3686440); // make sure that another event is, in fact, pended event = fake_event_get_last(); cl_assert_equal_i(event.type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(event.touch.type, PebbleTouchEvent_TouchesAvailable); TouchEventContext ctx = { .idx = 0 }; // handle first TouchesAvailable event touch_dispatch_touch_events(0, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 0); // handle second TouchesAvailable event touch_dispatch_touch_events(0, prv_touch_event_dispatch_cb, &ctx); cl_assert_equal_i(ctx.idx, 1); prv_test_touch_event(&ctx.touch_events[0], 0, TouchEvent_Touchdown, &GPoint(15, 15), 3686440, 6, NULL, 0, 0, false); } static PebbleEvent s_expected_palm_events[2]; static int s_palm_event_count = 0; static void prv_handle_palm_events(PebbleEvent *e) { s_expected_palm_events[s_palm_event_count++] = *e; } void test_touch__palm_detect_event(void) { touch_handle_update(0, TouchState_FingerDown, &GPoint(13, 13), 6, 3686420); touch_handle_update(1, TouchState_FingerDown, &GPoint(55, 55), 2, 3686480); fake_event_set_callback(prv_handle_palm_events); touch_handle_driver_event(TouchDriverEvent_PalmDetect); // Cancelled event, followed by a palm detection event cl_assert_equal_i(s_expected_palm_events[0].type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(s_expected_palm_events[0].touch.type, PebbleTouchEvent_TouchesCancelled); cl_assert_equal_i(s_expected_palm_events[1].type, PEBBLE_TOUCH_EVENT); cl_assert_equal_i(s_expected_palm_events[1].touch.type, PebbleTouchEvent_PalmDetected); TouchEvent *touch_event = touch_event_queue_get_event(0, 0); cl_assert_equal_p(touch_event, NULL); touch_event = touch_event_queue_get_event(1, 0); cl_assert_equal_p(touch_event, NULL); }