diff --git a/src/fw/services/touch/touch.c b/src/fw/services/touch/touch.c index 637626deb7..b3d6950888 100644 --- a/src/fw/services/touch/touch.c +++ b/src/fw/services/touch/touch.c @@ -63,19 +63,22 @@ static void prv_add_subscriber_cb(PebbleTask task) { static void prv_remove_subscriber_cb(PebbleTask task) { mutex_lock(s_touch_mutex); PBL_ASSERTN(s_subscriber_count > 0); - if (--s_subscriber_count == 0 && s_globally_enabled) { + const bool was_last_subscriber = (--s_subscriber_count == 0); + if (was_last_subscriber && s_globally_enabled) { touch_sensor_set_enabled(false); } - // An app whose shared touch subscription disappears (task exit, or both - // slots emptied) cannot have a live raw handler or nav dispatcher anymore; - // drop its raw-slot mark and the app-nav-active flag so a crashed app cannot - // leak backlight-follow behavior. + // An app whose shared touch subscription disappears (task exit, or both slots emptied) cannot have a live raw handler or nav dispatcher anymore; drop its raw-slot mark and the app-nav-active flag so a crashed app cannot leak backlight-follow behavior. if (task == PebbleTask_App) { s_raw_subscriber_tasks &= (uint8_t)~(1u << task); s_app_nav_active = false; } PBL_LOG_DBG("Touch: subscriber removed, count=%" PRIu8, s_subscriber_count); mutex_unlock(s_touch_mutex); + + if (was_last_subscriber) { + // A finger down when the last subscriber goes away never gets a Liftoff otherwise (the sensor stops reporting), so the backlight hold counter stays pinned. Same teardown the global toggle performs. + touch_release_active(); + } } void touch_init(void) { diff --git a/tests/fw/services/test_touch.c b/tests/fw/services/test_touch.c index eae9b16926..a7c47c20e9 100644 --- a/tests/fw/services/test_touch.c +++ b/tests/fw/services/test_touch.c @@ -591,6 +591,26 @@ void test_touch__reset_mid_gesture_refuses_continuation(void) { touch_handle_injected_update(TouchInjectPhase_End, 50, 60); } +void test_touch__last_unsubscribe_with_finger_down_emits_liftoff(void) { + // Finger down, then the last subscriber goes away: the sensor powers down mid-gesture, so a Liftoff must be synthesized with the last coordinates (not zeros) or the backlight hold taken by the Touchdown never unwinds — the same teardown the global toggle performs. + s_add_subscriber_cb(PebbleTask_App); + touch_handle_update(TouchState_FingerDown, 30, 40); + fake_event_reset_count(); + + s_remove_subscriber_cb(PebbleTask_App); + cl_assert_equal_i(fake_event_get_count(), 1); + prv_assert_touch_event(TouchEvent_Liftoff, 30, 40); +} + +void test_touch__last_unsubscribe_without_finger_no_liftoff(void) { + // No finger down: the last subscriber leaving must not fabricate a Liftoff. + s_add_subscriber_cb(PebbleTask_App); + fake_event_reset_count(); + + s_remove_subscriber_cb(PebbleTask_App); + cl_assert_equal_i(fake_event_get_count(), 0); +} + void test_touch__event_abi_unchanged(void) { // non_navigational rides in the padding after type:8; x/y offsets and the // overall size must not move, keeping the SDK struct app-compatible.