Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/fw/services/touch/touch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 20 additions & 0 deletions tests/fw/services/test_touch.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down