diff --git a/include/pbl/services/notifications/alerts_preferences_private.h b/include/pbl/services/notifications/alerts_preferences_private.h index fdc6ee1522..f5d7f824c5 100644 --- a/include/pbl/services/notifications/alerts_preferences_private.h +++ b/include/pbl/services/notifications/alerts_preferences_private.h @@ -91,6 +91,20 @@ bool alerts_preferences_dnd_is_smart_enabled(void); void alerts_preferences_dnd_set_smart_enabled(bool enable); +bool alerts_preferences_dnd_is_sleep_enabled(void); + +void alerts_preferences_dnd_set_sleep_enabled(bool enable); + +typedef enum { + DndUntilWakeStateDisabled = 0, + DndUntilWakeStateWaitingForSleep, + DndUntilWakeStateWaitingForWake, +} DndUntilWakeState; + +DndUntilWakeState alerts_preferences_dnd_get_until_wake_state(void); + +void alerts_preferences_dnd_set_until_wake_state(DndUntilWakeState state); + //! Lock the alerts preferences mutex. Must be paired with alerts_preferences_unlock(). void alerts_preferences_lock(void); diff --git a/include/pbl/services/notifications/do_not_disturb.h b/include/pbl/services/notifications/do_not_disturb.h index cb33e12951..9d6465e4df 100644 --- a/include/pbl/services/notifications/do_not_disturb.h +++ b/include/pbl/services/notifications/do_not_disturb.h @@ -38,6 +38,8 @@ typedef enum ManualDNDFirstUseSource { //! and automatically puts the watch into an Active DND Mode //! Scheduled DND - Allows the user to specify a daily schedule for when the DND should be in active //! mode. Once coming out of a schedule, if the Manual DND is enabled, it disables that setting. +//! Sleep Aware DND - Activates while activity tracking reports confirmed sleep. +//! Until Wake DND - Activates immediately and ends after confirmed sleep followed by waking. //! @return True if DND is in effect, false if not. bool do_not_disturb_is_active(void); @@ -61,6 +63,14 @@ bool do_not_disturb_is_smart_dnd_enabled(void); void do_not_disturb_toggle_smart_dnd(void); +bool do_not_disturb_is_sleep_dnd_enabled(void); + +void do_not_disturb_toggle_sleep_dnd(void); + +bool do_not_disturb_is_until_wake_enabled(void); + +void do_not_disturb_set_until_wake_enabled(bool enable); + void do_not_disturb_get_schedule(DoNotDisturbScheduleType type, DoNotDisturbSchedule *schedule_out); void do_not_disturb_set_schedule(DoNotDisturbScheduleType type, DoNotDisturbSchedule *schedule); @@ -81,4 +91,6 @@ void do_not_disturb_handle_pref_synced(void); void do_not_disturb_handle_calendar_event(PebbleCalendarEvent *e); +void do_not_disturb_handle_activity_event(PebbleActivityEvent *e); + void do_not_disturb_manual_toggle_with_dialog(void); diff --git a/src/fw/apps/system/settings/quiet_time.c b/src/fw/apps/system/settings/quiet_time.c index e5e4d90ef5..d46461c3a2 100644 --- a/src/fw/apps/system/settings/quiet_time.c +++ b/src/fw/apps/system/settings/quiet_time.c @@ -10,11 +10,13 @@ #include "applib/ui/menu_layer.h" #include "applib/ui/time_range_selection_window.h" #include "kernel/pbl_malloc.h" +#include "pbl/services/activity/activity.h" #include "pbl/services/clock.h" #include "pbl/services/i18n/i18n.h" #include "pbl/services/notifications/alerts_private.h" #include "pbl/services/notifications/do_not_disturb.h" #include "pbl/services/notifications/alerts_preferences.h" +#include "popups/health_tracking_ui.h" #include "system/passert.h" #include "pbl/util/size.h" #include "pbl/util/string.h" @@ -39,6 +41,7 @@ typedef struct { enum QuietTimeItem { QuietTimeItemManual, + QuietTimeItemUntilWake, QuietTimeItemSchedule, QuietTimeItemInterruptions, QuietTimeItemNotifications, @@ -55,6 +58,7 @@ enum QuietTimeItem { enum QuietTimeScheduleItem { QuietTimeScheduleItemCalendarAware, + QuietTimeScheduleItemSleepAware, QuietTimeScheduleItemWeekday, QuietTimeScheduleItemWeekend, QuietTimeScheduleItem_Count, @@ -258,6 +262,12 @@ static void prv_schedule_draw_row_cb(SettingsCallbacks *context, GContext *ctx, : i18n_ctx_get("QuietTime", "Disabled", data), buffer_length); break; + case QuietTimeScheduleItemSleepAware: + title = i18n_get("Sleep Aware", data); + strncpy(subtitle, do_not_disturb_is_sleep_dnd_enabled() ? + i18n_ctx_get("QuietTime", "Enabled", data) : + i18n_ctx_get("QuietTime", "Disabled", data), buffer_length); + break; case QuietTimeScheduleItemWeekday: title = i18n_get("Weekdays", data); if (do_not_disturb_is_schedule_enabled(WeekdaySchedule)) { @@ -288,6 +298,14 @@ static void prv_schedule_select_click_cb(SettingsCallbacks *context, uint16_t ro case QuietTimeScheduleItemCalendarAware: do_not_disturb_toggle_smart_dnd(); break; + case QuietTimeScheduleItemSleepAware: + if (!do_not_disturb_is_sleep_dnd_enabled() && + !activity_prefs_tracking_is_enabled()) { + health_tracking_ui_feature_show_disabled(); + break; + } + do_not_disturb_toggle_sleep_dnd(); + break; case QuietTimeScheduleItemWeekday: prv_scheduled_dnd_menu_push(WeekdaySchedule, data); break; @@ -428,6 +446,11 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx, const Lay subtitle = do_not_disturb_is_manually_enabled() ? i18n_get("On", data) : i18n_get("Off", data); break; + case QuietTimeItemUntilWake: + title = i18n_get("Until Wake", data); + subtitle = do_not_disturb_is_until_wake_enabled() ? + i18n_get("On", data) : i18n_get("Off", data); + break; case QuietTimeItemSchedule: title = i18n_get("Schedule", data); break; @@ -468,6 +491,14 @@ static void prv_select_click_cb(SettingsCallbacks *context, uint16_t row) { case QuietTimeItemManual: do_not_disturb_toggle_manually_enabled(ManualDNDFirstUseSourceSettingsMenu); break; + case QuietTimeItemUntilWake: + if (!do_not_disturb_is_until_wake_enabled() && + !activity_prefs_tracking_is_enabled()) { + health_tracking_ui_feature_show_disabled(); + break; + } + do_not_disturb_set_until_wake_enabled(!do_not_disturb_is_until_wake_enabled()); + break; case QuietTimeItemSchedule: prv_schedule_submenu_push(); break; diff --git a/src/fw/kernel/events.h b/src/fw/kernel/events.h index a452fe8349..719fbb6fd9 100644 --- a/src/fw/kernel/events.h +++ b/src/fw/kernel/events.h @@ -723,12 +723,14 @@ _Static_assert(sizeof(PebbleAppCacheEvent) == 5, "PebbleTimelinePeekEvent size m typedef enum PebbleActivityEventType { PebbleActivityEvent_TrackingStarted, PebbleActivityEvent_TrackingStopped, + PebbleActivityEvent_SleepStateChanged, PebbleActivityEventNum } PebbleActivityEventType; typedef struct PBL_PACKED PebbleActivityEvent { PebbleActivityEventType type : 8; + uint8_t sleep_state; } PebbleActivityEvent; typedef enum PebbleWorkoutEventType { diff --git a/src/fw/services/activity/activity_sessions.c b/src/fw/services/activity/activity_sessions.c index bd925d5907..35dcd9401e 100644 --- a/src/fw/services/activity/activity_sessions.c +++ b/src/fw/services/activity/activity_sessions.c @@ -434,6 +434,15 @@ static void prv_update_sleep_metrics(time_t now_utc, time_t max_end_utc, "state_min: %" PRIu32 "", sleep_data->total_minutes, sleep_data->restful_minutes, sleep_data->cur_state, sleep_data->cur_state_elapsed_minutes); + + PebbleEvent e = { + .type = PEBBLE_ACTIVITY_EVENT, + .activity_event = { + .type = PebbleActivityEvent_SleepStateChanged, + .sleep_state = sleep_data->cur_state, + }, + }; + event_put(&e); } } unlock: diff --git a/src/fw/services/blob_db/settings_blob_db.c b/src/fw/services/blob_db/settings_blob_db.c index e669af86a6..c7fc023382 100644 --- a/src/fw/services/blob_db/settings_blob_db.c +++ b/src/fw/services/blob_db/settings_blob_db.c @@ -137,6 +137,7 @@ static const char *s_syncable_notif_prefs[] = { "vibeScoreAlarms", "dndManuallyEnabled", "dndSmartEnabled", + "dndSleepEnabled", "dndWeekdaySchedule", "dndWeekdayScheduleEnabled", "dndWeekendSchedule", diff --git a/src/fw/services/notifications/alerts_preferences.c b/src/fw/services/notifications/alerts_preferences.c index 7a08903c95..8af3a8d8cd 100644 --- a/src/fw/services/notifications/alerts_preferences.c +++ b/src/fw/services/notifications/alerts_preferences.c @@ -76,6 +76,12 @@ static bool s_do_not_disturb_manually_enabled = false; #define PREF_KEY_DND_SMART_ENABLED "dndSmartEnabled" static bool s_do_not_disturb_smart_dnd_enabled = false; +#define PREF_KEY_DND_SLEEP_ENABLED "dndSleepEnabled" +static bool s_do_not_disturb_sleep_dnd_enabled = false; + +#define PREF_KEY_DND_UNTIL_WAKE_STATE "dndUntilWakeState" +static DndUntilWakeState s_do_not_disturb_until_wake_state = DndUntilWakeStateDisabled; + #define PREF_KEY_FIRST_USE_COMPLETE "firstUseComplete" static uint32_t s_first_use_complete = 0; @@ -320,6 +326,8 @@ void alerts_preferences_init(void) { RESTORE_PREF(PREF_KEY_VIBE_SCORE_ON_DISCONNECT, s_vibe_score_on_disconnect); RESTORE_PREF(PREF_KEY_DND_MANUALLY_ENABLED, s_do_not_disturb_manually_enabled); RESTORE_PREF(PREF_KEY_DND_SMART_ENABLED, s_do_not_disturb_smart_dnd_enabled); + RESTORE_PREF(PREF_KEY_DND_SLEEP_ENABLED, s_do_not_disturb_sleep_dnd_enabled); + RESTORE_PREF(PREF_KEY_DND_UNTIL_WAKE_STATE, s_do_not_disturb_until_wake_state); RESTORE_PREF(PREF_KEY_DND_INTERRUPTIONS_MASK, s_dnd_interruptions_mask); RESTORE_PREF(PREF_KEY_DND_SHOW_NOTIFICATIONS, s_dnd_show_notifications); RESTORE_PREF(PREF_KEY_DND_MOTION_BACKLIGHT, s_dnd_motion_backlight); @@ -656,6 +664,24 @@ void alerts_preferences_dnd_set_smart_enabled(bool enable) { SET_PREF(PREF_KEY_DND_SMART_ENABLED, s_do_not_disturb_smart_dnd_enabled); } +bool alerts_preferences_dnd_is_sleep_enabled(void) { + return s_do_not_disturb_sleep_dnd_enabled; +} + +void alerts_preferences_dnd_set_sleep_enabled(bool enable) { + s_do_not_disturb_sleep_dnd_enabled = enable; + SET_PREF(PREF_KEY_DND_SLEEP_ENABLED, s_do_not_disturb_sleep_dnd_enabled); +} + +DndUntilWakeState alerts_preferences_dnd_get_until_wake_state(void) { + return s_do_not_disturb_until_wake_state; +} + +void alerts_preferences_dnd_set_until_wake_state(DndUntilWakeState state) { + s_do_not_disturb_until_wake_state = state; + SET_PREF(PREF_KEY_DND_UNTIL_WAKE_STATE, s_do_not_disturb_until_wake_state); +} + void alerts_preferences_lock(void) { pbl_mutex_lock(&s_mutex, PBL_FOREVER); } @@ -667,7 +693,8 @@ void alerts_preferences_unlock(void) { //! Keys that feed do_not_disturb_is_active() or the DND schedule timer static bool prv_is_dnd_state_key(const char *key) { if (strcmp(key, PREF_KEY_DND_MANUALLY_ENABLED) == 0 || - strcmp(key, PREF_KEY_DND_SMART_ENABLED) == 0) { + strcmp(key, PREF_KEY_DND_SMART_ENABLED) == 0 || + strcmp(key, PREF_KEY_DND_SLEEP_ENABLED) == 0) { return true; } for (int i = 0; i < NumDNDSchedules; i++) { @@ -723,6 +750,7 @@ void alerts_preferences_handle_blob_db_event(PebbleBlobDBEvent *event) { RELOAD_IF_MATCH(PREF_KEY_VIBE_SCORE_ALARMS, s_vibe_score_alarms); RELOAD_IF_MATCH(PREF_KEY_DND_MANUALLY_ENABLED, s_do_not_disturb_manually_enabled); RELOAD_IF_MATCH(PREF_KEY_DND_SMART_ENABLED, s_do_not_disturb_smart_dnd_enabled); + RELOAD_IF_MATCH(PREF_KEY_DND_SLEEP_ENABLED, s_do_not_disturb_sleep_dnd_enabled); RELOAD_IF_MATCH(s_dnd_schedule_keys[WeekdaySchedule].schedule_pref_key, s_dnd_schedule[WeekdaySchedule].schedule); RELOAD_IF_MATCH(s_dnd_schedule_keys[WeekdaySchedule].enabled_pref_key, diff --git a/src/fw/services/notifications/do_not_disturb.c b/src/fw/services/notifications/do_not_disturb.c index 5ec1253415..d030ca7342 100644 --- a/src/fw/services/notifications/do_not_disturb.c +++ b/src/fw/services/notifications/do_not_disturb.c @@ -32,8 +32,10 @@ PBL_LOG_MODULE_DECLARE(service_notifications, CONFIG_SERVICE_NOTIFICATIONS_LOG_LEVEL); typedef struct DoNotDisturbData { + ActivitySleepState sleep_state; bool is_in_schedule_period; bool manually_override_dnd; + bool sleep_dnd_override; bool was_active; } DoNotDisturbData; @@ -49,6 +51,8 @@ static struct pbl_cron_job s_schedule_switch_job; static bool prv_is_smart_dnd_active(void); static bool prv_is_schedule_active(void); +static bool prv_is_sleep_dnd_active(void); +static bool prv_is_until_wake_active(void); static void prv_update_schedule_mode(void); static void prv_update_active_time(bool is_active) { @@ -237,6 +241,67 @@ static bool prv_is_smart_dnd_active(void) { !s_data.manually_override_dnd); } +static bool prv_sleep_state_is_asleep(ActivitySleepState state) { + return state == ActivitySleepStateLightSleep || state == ActivitySleepStateRestfulSleep; +} + +static bool prv_is_sleep_dnd_active(void) { + return do_not_disturb_is_sleep_dnd_enabled() && + prv_sleep_state_is_asleep(s_data.sleep_state) && + !s_data.sleep_dnd_override; +} + +static ActivitySleepState prv_get_sleep_state(void) { + int32_t sleep_state; + if (!activity_tracking_on() || + !activity_get_metric(ActivityMetricSleepState, 1, &sleep_state)) { + return ActivitySleepStateUnknown; + } + + switch (sleep_state) { + case ActivitySleepStateAwake: + case ActivitySleepStateRestfulSleep: + case ActivitySleepStateLightSleep: + case ActivitySleepStateUnknown: + return sleep_state; + } + return ActivitySleepStateUnknown; +} + +static bool prv_is_until_wake_active(void) { + const DndUntilWakeState state = alerts_preferences_dnd_get_until_wake_state(); + return state == DndUntilWakeStateWaitingForSleep || + state == DndUntilWakeStateWaitingForWake; +} + +static void prv_update_until_wake_state(void) { + const DndUntilWakeState state = alerts_preferences_dnd_get_until_wake_state(); + if (state == DndUntilWakeStateWaitingForSleep && + prv_sleep_state_is_asleep(s_data.sleep_state)) { + alerts_preferences_dnd_set_until_wake_state(DndUntilWakeStateWaitingForWake); + } else if (state == DndUntilWakeStateWaitingForWake && + s_data.sleep_state == ActivitySleepStateAwake) { + alerts_preferences_dnd_set_until_wake_state(DndUntilWakeStateDisabled); + } else if (state != DndUntilWakeStateDisabled && + state != DndUntilWakeStateWaitingForSleep && + state != DndUntilWakeStateWaitingForWake) { + alerts_preferences_dnd_set_until_wake_state(DndUntilWakeStateDisabled); + } +} + +static void prv_set_sleep_state(ActivitySleepState sleep_state) { + s_data.sleep_state = sleep_state; + + // Only clear the manual override when we've definitively observed waking up. + // This preserves the override across temporary tracking interruptions. + if (sleep_state == ActivitySleepStateAwake) { + s_data.sleep_dnd_override = false; + } + + prv_update_until_wake_state(); + prv_do_update(); +} + /////////////////////////////////////////////////////////////////////////////////////////////////// //! Public Functions /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -247,7 +312,7 @@ DEFINE_SYSCALL(bool, sys_do_not_disturb_is_active, void) { bool do_not_disturb_is_active(void) { if (do_not_disturb_is_manually_enabled() || prv_is_schedule_active() || - prv_is_smart_dnd_active()) { + prv_is_smart_dnd_active() || prv_is_sleep_dnd_active() || prv_is_until_wake_active()) { return true; } return false; @@ -258,14 +323,24 @@ bool do_not_disturb_is_manually_enabled(void) { } void do_not_disturb_set_manually_enabled(bool enable) { - const bool is_auto_dnd = + const bool was_manually_enabled = do_not_disturb_is_manually_enabled(); + const bool is_schedule_or_smart_dnd_active = + prv_is_schedule_active() || prv_is_smart_dnd_active(); + const bool is_schedule_or_smart_dnd_enabled = prv_is_current_schedule_enabled() || do_not_disturb_is_smart_dnd_enabled(); + const bool is_sleep_dnd_active = prv_is_sleep_dnd_active(); const bool was_active = do_not_disturb_is_active(); alerts_preferences_dnd_set_manually_enabled(enable); - // Turning the manual DND OFF in an active DND mode overrides the automatic mode - if (!enable && was_active && is_auto_dnd) { - s_data.manually_override_dnd = true; + // Turning manual DND off overrides any automatic modes that are currently in effect. + if (!enable && was_active) { + if (is_schedule_or_smart_dnd_active || + (was_manually_enabled && is_schedule_or_smart_dnd_enabled)) { + s_data.manually_override_dnd = true; + } + if (is_sleep_dnd_active) { + s_data.sleep_dnd_override = true; + } } prv_do_update(); } @@ -295,6 +370,30 @@ void do_not_disturb_toggle_smart_dnd(void) { } } +bool do_not_disturb_is_sleep_dnd_enabled(void) { + return alerts_preferences_dnd_is_sleep_enabled(); +} + +void do_not_disturb_toggle_sleep_dnd(void) { + alerts_preferences_dnd_set_sleep_enabled(!alerts_preferences_dnd_is_sleep_enabled()); + s_data.sleep_dnd_override = false; + prv_do_update(); +} + +bool do_not_disturb_is_until_wake_enabled(void) { + return prv_is_until_wake_active(); +} + +void do_not_disturb_set_until_wake_enabled(bool enable) { + DndUntilWakeState state = DndUntilWakeStateDisabled; + if (enable) { + state = prv_sleep_state_is_asleep(s_data.sleep_state) ? + DndUntilWakeStateWaitingForWake : DndUntilWakeStateWaitingForSleep; + } + alerts_preferences_dnd_set_until_wake_state(state); + prv_do_update(); +} + void do_not_disturb_get_schedule(DoNotDisturbScheduleType type, DoNotDisturbSchedule *schedule_out) { alerts_preferences_dnd_get_schedule(type, schedule_out); @@ -322,8 +421,10 @@ void do_not_disturb_toggle_scheduled(DoNotDisturbScheduleType type) { void do_not_disturb_init(void) { s_data = (DoNotDisturbData){ + .sleep_state = prv_get_sleep_state(), .was_active = false, }; + prv_update_until_wake_state(); prv_try_update_schedule_mode((void *)true); } @@ -339,6 +440,22 @@ void do_not_disturb_handle_calendar_event(PebbleCalendarEvent *e) { prv_do_update(); } +void do_not_disturb_handle_activity_event(PebbleActivityEvent *e) { + switch (e->type) { + case PebbleActivityEvent_TrackingStarted: + prv_set_sleep_state(prv_get_sleep_state()); + break; + case PebbleActivityEvent_TrackingStopped: + prv_set_sleep_state(ActivitySleepStateUnknown); + break; + case PebbleActivityEvent_SleepStateChanged: + prv_set_sleep_state(e->sleep_state); + break; + case PebbleActivityEventNum: + break; + } +} + void do_not_disturb_manual_toggle_with_dialog(void) { do_not_disturb_toggle_push(ActionTogglePrompt_Auto, false /* set_exit_reason */); } diff --git a/src/fw/services/notifications/do_not_disturb_toggle.c b/src/fw/services/notifications/do_not_disturb_toggle.c index c261a3a0b6..83a0db64b1 100644 --- a/src/fw/services/notifications/do_not_disturb_toggle.c +++ b/src/fw/services/notifications/do_not_disturb_toggle.c @@ -18,6 +18,9 @@ static bool prv_get_state(void *context) { static void prv_set_state(bool enabled, void *context) { PBL_LOG_DBG("Manual DND toggle: %s", enabled ? "enabled" : "disabled"); + if (!enabled) { + do_not_disturb_set_until_wake_enabled(false); + } do_not_disturb_set_manually_enabled(enabled); } diff --git a/src/fw/shell/normal/shell_event_loop.c b/src/fw/shell/normal/shell_event_loop.c index d8182ab27b..acda24bc04 100644 --- a/src/fw/shell/normal/shell_event_loop.c +++ b/src/fw/shell/normal/shell_event_loop.c @@ -157,6 +157,7 @@ void shell_event_loop_handle_event(PebbleEvent *e) { return; case PEBBLE_ACTIVITY_EVENT: + do_not_disturb_handle_activity_event(&e->activity_event); workout_service_activity_event_handler(&e->activity_event); return; diff --git a/tests/fw/services/activity/test_activity.c b/tests/fw/services/activity/test_activity.c index 68ece3424f..ab215097c9 100644 --- a/tests/fw/services/activity/test_activity.c +++ b/tests/fw/services/activity/test_activity.c @@ -2002,11 +2002,17 @@ void test_activity__migrate_settings_v2(void) { // fake_event callback used to look for sleep events generated by the health_events test static PebbleEvent s_captured_sleep_event = {}; static int s_num_captured_sleep_events = 0; +static int s_num_captured_sleep_state_events = 0; +static uint32_t s_captured_sleep_state_mask = 0; static void prv_fake_sleep_event_cb(PebbleEvent *event) { if ((event->type == PEBBLE_HEALTH_SERVICE_EVENT) && (event->health_event.type == HealthEventSleepUpdate)) { s_captured_sleep_event = *event; s_num_captured_sleep_events++; + } else if ((event->type == PEBBLE_ACTIVITY_EVENT) + && (event->activity_event.type == PebbleActivityEvent_SleepStateChanged)) { + s_num_captured_sleep_state_events++; + s_captured_sleep_state_mask |= 1 << event->activity_event.sleep_state; } } @@ -2056,6 +2062,8 @@ void test_activity__health_events(void) { fake_event_set_callback(prv_fake_sleep_event_cb); s_captured_sleep_event = (PebbleEvent){}; s_num_captured_sleep_events = 0; + s_num_captured_sleep_state_events = 0; + s_captured_sleep_state_mask = 0; prv_feed_canned_accel_data(60 * SECONDS_PER_MINUTE, 0, ActivitySleepStateLightSleep); prv_feed_canned_accel_data(50 * SECONDS_PER_MINUTE, 0, ActivitySleepStateRestfulSleep); @@ -2075,6 +2083,13 @@ void test_activity__health_events(void) { cl_assert_equal_i(event.type, PEBBLE_HEALTH_SERVICE_EVENT); cl_assert_equal_i(event.health_event.type, HealthEventSleepUpdate); + const uint32_t expected_sleep_state_mask = + (1 << ActivitySleepStateAwake) | + (1 << ActivitySleepStateRestfulSleep) | + (1 << ActivitySleepStateLightSleep); + cl_assert_equal_i(s_num_captured_sleep_state_events, 6); + cl_assert_equal_i(s_captured_sleep_state_mask, expected_sleep_state_mask); + // ----------------------------------- // Test that we receive history update events fake_event_reset_count(); diff --git a/tests/fw/services/test_do_not_disturb.c b/tests/fw/services/test_do_not_disturb.c index 3051d23b41..af5ce3806f 100644 --- a/tests/fw/services/test_do_not_disturb.c +++ b/tests/fw/services/test_do_not_disturb.c @@ -4,6 +4,7 @@ #include "pbl/services/notifications/do_not_disturb.h" #include "applib/ui/action_toggle.h" +#include "pbl/services/activity/activity.h" #include "kernel/events.h" #include "resource/resource.h" #include @@ -47,8 +48,13 @@ #include "fake_spi_flash.h" #define PREF_KEY_DND_MANUALLY_ENABLED "dndManuallyEnabled" +#define PREF_KEY_DND_SLEEP_ENABLED "dndSleepEnabled" +#define PREF_KEY_DND_UNTIL_WAKE_STATE "dndUntilWakeState" static int s_num_dnd_events_put = 0; +static bool s_last_dnd_event_active = false; +static bool s_is_activity_tracking = true; +static ActivitySleepState s_activity_sleep_state = ActivitySleepStateAwake; /////////////////////////////////////////////////////////////////////////////////////////////////// //! Fakes @@ -61,9 +67,21 @@ bool system_task_add_callback(SystemTaskEventCallback cb, void *data) { void event_put(PebbleEvent *event) { if (event->type == PEBBLE_DO_NOT_DISTURB_EVENT) { s_num_dnd_events_put++; + s_last_dnd_event_active = event->do_not_disturb.is_active; } } +bool activity_tracking_on(void) { + return s_is_activity_tracking; +} + +bool activity_get_metric(ActivityMetric metric, uint32_t history_len, int32_t *history) { + cl_assert_equal_i(metric, ActivityMetricSleepState); + cl_assert_equal_i(history_len, 1); + *history = s_activity_sleep_state; + return true; +} + // Thursday, March 12, 2015, 00:00 UTC static const int s_thursday_00_00 = 1426118400; // Thursday, March 12, 2015, 01:00 UTC @@ -131,6 +149,15 @@ static void prv_assert_manually_dnd_setting_val(bool expected_value) { sizeof(bool)); } +static void prv_send_sleep_state(ActivitySleepState sleep_state) { + s_activity_sleep_state = sleep_state; + PebbleActivityEvent event = { + .type = PebbleActivityEvent_SleepStateChanged, + .sleep_state = sleep_state, + }; + do_not_disturb_handle_activity_event(&event); +} + /////////////////////////////////////////////////////////////////////////////////////////////////// //! Tests @@ -140,8 +167,12 @@ void test_do_not_disturb__initialize(void) { pfs_format(false); rtc_set_time(s_thursday_00_00); + s_is_activity_tracking = true; + s_activity_sleep_state = ActivitySleepStateAwake; alerts_preferences_init(); pbl_cron_init(); + alerts_preferences_dnd_set_sleep_enabled(false); + alerts_preferences_dnd_set_until_wake_state(DndUntilWakeStateDisabled); do_not_disturb_init(); do_not_disturb_set_manually_enabled(false); @@ -155,6 +186,7 @@ void test_do_not_disturb__initialize(void) { s_event_ongoing = false; s_num_dnd_events_put = 0; + s_last_dnd_event_active = false; } void test_do_not_disturb__cleanup(void) { @@ -162,6 +194,10 @@ void test_do_not_disturb__cleanup(void) { do_not_disturb_set_manually_enabled(false); do_not_disturb_set_schedule_enabled(WeekdaySchedule, false); do_not_disturb_set_schedule_enabled(WeekendSchedule, false); + if (do_not_disturb_is_sleep_dnd_enabled()) { + do_not_disturb_toggle_sleep_dnd(); + } + do_not_disturb_set_until_wake_enabled(false); pbl_cron_deinit(); } @@ -223,6 +259,177 @@ void test_do_not_disturb__manual_pref_synced_from_phone(void) { cl_assert_equal_i(s_num_dnd_events_put, 2); } +void test_do_not_disturb__sleep_dnd(void) { + cl_assert(!do_not_disturb_is_sleep_dnd_enabled()); + cl_assert(!do_not_disturb_is_active()); + + prv_send_sleep_state(ActivitySleepStateLightSleep); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 0); + + do_not_disturb_toggle_sleep_dnd(); + cl_assert(do_not_disturb_is_sleep_dnd_enabled()); + cl_assert(do_not_disturb_is_active()); + cl_assert(s_last_dnd_event_active); + cl_assert_equal_i(s_num_dnd_events_put, 1); + prv_assert_settings_value(PREF_KEY_DND_SLEEP_ENABLED, strlen(PREF_KEY_DND_SLEEP_ENABLED), + &(bool){true}, sizeof(bool)); + + prv_send_sleep_state(ActivitySleepStateRestfulSleep); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 1); + + do_not_disturb_set_manually_enabled(false); + cl_assert(!do_not_disturb_is_active()); + cl_assert(!s_last_dnd_event_active); + cl_assert_equal_i(s_num_dnd_events_put, 2); + + prv_send_sleep_state(ActivitySleepStateLightSleep); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 2); + + prv_send_sleep_state(ActivitySleepStateAwake); + cl_assert(!do_not_disturb_is_active()); + prv_send_sleep_state(ActivitySleepStateLightSleep); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 3); + + do_not_disturb_set_manually_enabled(true); + prv_send_sleep_state(ActivitySleepStateAwake); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 3); + + do_not_disturb_set_manually_enabled(false); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 4); +} + +void test_do_not_disturb__sleep_pref_synced_from_phone(void) { + prv_send_sleep_state(ActivitySleepStateLightSleep); + prv_sync_bool_pref_from_phone(PREF_KEY_DND_SLEEP_ENABLED, true); + cl_assert(do_not_disturb_is_sleep_dnd_enabled()); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 1); + + prv_sync_bool_pref_from_phone(PREF_KEY_DND_SLEEP_ENABLED, false); + cl_assert(!do_not_disturb_is_sleep_dnd_enabled()); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 2); +} + +void test_do_not_disturb__sleep_dnd_tracks_activity_service(void) { + do_not_disturb_toggle_sleep_dnd(); + prv_send_sleep_state(ActivitySleepStateLightSleep); + cl_assert(do_not_disturb_is_active()); + + s_is_activity_tracking = false; + PebbleActivityEvent event = { + .type = PebbleActivityEvent_TrackingStopped, + }; + do_not_disturb_handle_activity_event(&event); + cl_assert(!do_not_disturb_is_active()); + + s_is_activity_tracking = true; + event.type = PebbleActivityEvent_TrackingStarted; + do_not_disturb_handle_activity_event(&event); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 3); +} + +void test_do_not_disturb__until_wake(void) { + cl_assert(!do_not_disturb_is_until_wake_enabled()); + cl_assert(!do_not_disturb_is_active()); + + do_not_disturb_set_until_wake_enabled(true); + cl_assert(do_not_disturb_is_until_wake_enabled()); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 1); + prv_assert_settings_value(PREF_KEY_DND_UNTIL_WAKE_STATE, + strlen(PREF_KEY_DND_UNTIL_WAKE_STATE), + &(DndUntilWakeState){DndUntilWakeStateWaitingForSleep}, + sizeof(DndUntilWakeState)); + + prv_send_sleep_state(ActivitySleepStateAwake); + cl_assert(do_not_disturb_is_until_wake_enabled()); + prv_send_sleep_state(ActivitySleepStateLightSleep); + cl_assert(do_not_disturb_is_until_wake_enabled()); + prv_assert_settings_value(PREF_KEY_DND_UNTIL_WAKE_STATE, + strlen(PREF_KEY_DND_UNTIL_WAKE_STATE), + &(DndUntilWakeState){DndUntilWakeStateWaitingForWake}, + sizeof(DndUntilWakeState)); + + prv_send_sleep_state(ActivitySleepStateRestfulSleep); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 1); + + prv_send_sleep_state(ActivitySleepStateAwake); + cl_assert(!do_not_disturb_is_until_wake_enabled()); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 2); +} + +void test_do_not_disturb__until_wake_enabled_while_asleep(void) { + prv_send_sleep_state(ActivitySleepStateLightSleep); + do_not_disturb_set_until_wake_enabled(true); + cl_assert(do_not_disturb_is_until_wake_enabled()); + prv_assert_settings_value(PREF_KEY_DND_UNTIL_WAKE_STATE, + strlen(PREF_KEY_DND_UNTIL_WAKE_STATE), + &(DndUntilWakeState){DndUntilWakeStateWaitingForWake}, + sizeof(DndUntilWakeState)); + + prv_send_sleep_state(ActivitySleepStateAwake); + cl_assert(!do_not_disturb_is_until_wake_enabled()); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 2); +} + +void test_do_not_disturb__until_wake_preserves_manual_dnd(void) { + do_not_disturb_set_manually_enabled(true); + do_not_disturb_set_until_wake_enabled(true); + prv_send_sleep_state(ActivitySleepStateLightSleep); + prv_send_sleep_state(ActivitySleepStateAwake); + + cl_assert(!do_not_disturb_is_until_wake_enabled()); + cl_assert(do_not_disturb_is_manually_enabled()); + cl_assert(do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 1); + + do_not_disturb_set_manually_enabled(false); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 2); +} + +void test_do_not_disturb__until_wake_tracks_activity_service(void) { + do_not_disturb_set_until_wake_enabled(true); + + s_is_activity_tracking = false; + PebbleActivityEvent event = { + .type = PebbleActivityEvent_TrackingStopped, + }; + do_not_disturb_handle_activity_event(&event); + cl_assert(do_not_disturb_is_until_wake_enabled()); + cl_assert(do_not_disturb_is_active()); + + s_is_activity_tracking = true; + s_activity_sleep_state = ActivitySleepStateLightSleep; + event.type = PebbleActivityEvent_TrackingStarted; + do_not_disturb_handle_activity_event(&event); + cl_assert(do_not_disturb_is_until_wake_enabled()); + + s_is_activity_tracking = false; + event.type = PebbleActivityEvent_TrackingStopped; + do_not_disturb_handle_activity_event(&event); + cl_assert(do_not_disturb_is_until_wake_enabled()); + + s_is_activity_tracking = true; + s_activity_sleep_state = ActivitySleepStateAwake; + event.type = PebbleActivityEvent_TrackingStarted; + do_not_disturb_handle_activity_event(&event); + cl_assert(!do_not_disturb_is_until_wake_enabled()); + cl_assert(!do_not_disturb_is_active()); + cl_assert_equal_i(s_num_dnd_events_put, 2); +} + void test_do_not_disturb__manually_enable_active(void) { bool active; active = do_not_disturb_is_active();