/* * 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 "applib/data_logging.h" #include "applib/health_service.h" #include "kernel/events.h" #include "kernel/pbl_malloc.h" #include "os/mutex.h" #include "os/tick.h" #include "services/common/analytics/analytics_event.h" #include "services/normal/alarms/alarm.h" #include "syscall/syscall.h" #include "syscall/syscall_internal.h" #include "system/passert.h" #include "util/size.h" #include #include "activity.h" #include "activity_algorithm.h" #include "activity_insights.h" #include "activity_private.h" // ------------------------------------------------------------------------------------ // Figure out the cutoff times for sleep and step activities for today given the current time static void prv_get_earliest_end_times_utc(time_t utc_sec, time_t *sleep_earliest_end_utc, time_t *step_earliest_end_utc) { time_t start_of_today_utc = time_util_get_midnight_of(utc_sec); int last_sleep_second_of_day = ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY * SECONDS_PER_MINUTE; *sleep_earliest_end_utc = start_of_today_utc - (SECONDS_PER_DAY - last_sleep_second_of_day); *step_earliest_end_utc = start_of_today_utc; } // ------------------------------------------------------------------------------------ // Remove all activity sessions that are older than "today", those that are invalid because they // are in the future, and optionally those that are still ongoing. void activity_sessions_prv_remove_out_of_range_activity_sessions(time_t utc_sec, bool remove_ongoing) { ActivityState *state = activity_private_state(); uint16_t num_sessions_to_clear = 0; uint16_t *session_entries = &state->activity_sessions_count; ActivitySession *sessions = state->activity_sessions; // Figure out the cutoff times for sleep and step activities time_t sleep_earliest_end_utc; time_t step_earliest_end_utc; prv_get_earliest_end_times_utc(utc_sec, &sleep_earliest_end_utc, &step_earliest_end_utc); for (uint32_t i = 0; i < *session_entries; i++) { time_t end_utc; if (activity_sessions_prv_is_sleep_activity(sessions[i].type)) { end_utc = sleep_earliest_end_utc; } else { end_utc = step_earliest_end_utc; } // See if we should keep this activity time_t end_time = sessions[i].start_utc + (sessions[i].length_min * SECONDS_PER_MINUTE); if ((end_time >= end_utc) && (end_time <= utc_sec) && (!remove_ongoing || !sessions[i].ongoing)) { // Keep it continue; } // This one needs to be removed uint32_t remaining = *session_entries - i - 1; memcpy(&sessions[i], &sessions[i + 1], remaining * sizeof(*sessions)); (*session_entries)--; num_sessions_to_clear++; i--; } // Zero out unused sessions at end. This is important because when we re-init from stored // settings, we detect the number of sessions we have by checking for non-zero ones memset(&sessions[*session_entries], 0, num_sessions_to_clear * sizeof(ActivitySession)); } // ------------------------------------------------------------------------------------ // Return true if the given activity type is a sleep activity bool activity_sessions_prv_is_sleep_activity(ActivitySessionType activity_type) { switch (activity_type) { case ActivitySessionType_Sleep: case ActivitySessionType_RestfulSleep: case ActivitySessionType_Nap: case ActivitySessionType_RestfulNap: return true; case ActivitySessionType_Walk: case ActivitySessionType_Run: case ActivitySessionType_Open: return false; case ActivitySessionType_None: case ActivitySessionTypeCount: break; } WTF; } // ------------------------------------------------------------------------------------ // Return true if this is a valid activity session static bool prv_is_valid_activity_session(ActivitySession *session) { // Make sure the type is valid switch (session->type) { case ActivitySessionType_Sleep: case ActivitySessionType_RestfulSleep: case ActivitySessionType_Nap: case ActivitySessionType_RestfulNap: case ActivitySessionType_Walk: case ActivitySessionType_Run: case ActivitySessionType_Open: break; case ActivitySessionType_None: case ActivitySessionTypeCount: PBL_LOG(LOG_LEVEL_WARNING, "Invalid activity type: %d", (int)session->type); return false; } // The length must be reasonable if (session->length_min > ACTIVITY_SESSION_MAX_LENGTH_MIN) { PBL_LOG(LOG_LEVEL_WARNING, "Invalid duration: %"PRIu16" ", session->length_min); return false; } // The flags must be valid if (session->reserved != 0) { PBL_LOG(LOG_LEVEL_WARNING, "Invalid flags: %d", (int)session->reserved); return false; } return true; } // ------------------------------------------------------------------------------------ // Return true if two activity sessions are equal in their type and start time // @param[in] session_a ptr to first session // @param[in] session_b ptr to second session // @param[in] any_sleep if true, a match occurs if session_a and session_b are both sleep // activities, even if they are different types of sleep static bool prv_activity_sessions_equal(ActivitySession *session_a, ActivitySession *session_b, bool any_sleep) { bool type_matches; const bool a_is_sleep = activity_sessions_prv_is_sleep_activity(session_a->type); const bool b_is_sleep = activity_sessions_prv_is_sleep_activity(session_b->type); if (any_sleep && a_is_sleep && b_is_sleep) { type_matches = true; } else { type_matches = (session_a->type == session_b->type); } return type_matches && (session_a->start_utc == session_b->start_utc); } // ------------------------------------------------------------------------------------ // Register a new activity. Called by the algorithm code when it detects a new activity. // If we already have this activity registered, it is updated. void activity_sessions_prv_add_activity_session(ActivitySession *session) { ActivityState *state = activity_private_state(); mutex_lock_recursive(state->mutex); { if (!session->ongoing) { state->need_activities_saved = true; } // Modifying a sleep session? if (activity_sessions_prv_is_sleep_activity(session->type)) { state->sleep_sessions_modified = true; } // If this is an existing activity, update it ActivitySession *stored_session = state->activity_sessions; for (uint16_t i = 0; i < state->activity_sessions_count; i++, stored_session++) { if (prv_activity_sessions_equal(session, stored_session, true /*any_sleep*/)) { state->activity_sessions[i] = *session; goto unlock; } } // If no more room, fail if (state->activity_sessions_count >= ACTIVITY_MAX_ACTIVITY_SESSIONS_COUNT) { PBL_LOG(LOG_LEVEL_WARNING, "No more room for additional activities"); goto unlock; } // Add this activity in PBL_LOG(LOG_LEVEL_INFO, "Adding activity session %d, start_time: %"PRIu32, (int)session->type, (uint32_t)session->start_utc); state->activity_sessions[state->activity_sessions_count++] = *session; } unlock: mutex_unlock_recursive(state->mutex); } // ------------------------------------------------------------------------------------ // Delete an ongoing activity. Called by the algorithm code when it decides that an activity // that was previously ongoing should not be registered after all. void activity_sessions_prv_delete_activity_session(ActivitySession *session) { ActivityState *state = activity_private_state(); mutex_lock_recursive(state->mutex); { // Look for this activity int found_session_idx = -1; ActivitySession *stored_session = state->activity_sessions; for (uint16_t i = 0; i < state->activity_sessions_count; i++, stored_session++) { if (prv_activity_sessions_equal(session, stored_session, false /*any_sleep*/)) { found_session_idx = i; break; } } // If session not found, do nothing if (found_session_idx < 0) { PBL_LOG(LOG_LEVEL_WARNING, "Session to delete not found"); goto unlock; } // The session we are deleting must be ongoing PBL_ASSERT(stored_session->ongoing, "Only ongoing sessions can be deleted"); // Remove this session int num_to_move = state->activity_sessions_count - found_session_idx - 1; PBL_ASSERTN(num_to_move >= 0); if (num_to_move == 0) { memset(&state->activity_sessions[found_session_idx], 0, sizeof(ActivitySession)); } else { memmove(&state->activity_sessions[found_session_idx], &state->activity_sessions[found_session_idx + 1], num_to_move * sizeof(ActivitySession)); } state->activity_sessions_count--; } unlock: mutex_unlock_recursive(state->mutex); } // -------------------------------------------------------------------------------------------- // Compute the total number of restful sleep seconds within a range of time static uint32_t prv_sleep_restful_seconds(uint32_t num_sessions, ActivitySession *sessions, time_t start_utc, time_t end_utc) { // Iterate through the sleep sessions, accumulating the total restful seconds seen between // start_utc and end_utc ActivitySession *session = sessions; uint32_t restful_sec = 0; for (uint32_t i = 0; i < num_sessions; i++, session++) { if ((session->type != ActivitySessionType_RestfulSleep) && (session->type != ActivitySessionType_RestfulNap)) { continue; } if ((session->start_utc >= start_utc) && ((time_t)(session->start_utc + (session->length_min * SECONDS_PER_MINUTE)) <= end_utc)) { restful_sec += session->length_min * SECONDS_PER_MINUTE; } } return restful_sec; } // -------------------------------------------------------------------------------------------- // Send an activity session (including sleep sessions) to data logging void activity_sessions_prv_send_activity_session_to_data_logging(ActivitySession *session) { ActivityState *state = activity_private_state(); time_t start_local = time_utc_to_local(session->start_utc); ActivitySessionDataLoggingRecord dls_record = { .version = ACTIVITY_SESSION_LOGGING_VERSION, .size = sizeof(ActivitySessionDataLoggingRecord), .activity = session->type, .utc_to_local = start_local - session->start_utc, .start_utc = (uint32_t)session->start_utc, .elapsed_sec = session->length_min * SECONDS_PER_MINUTE, }; if (activity_sessions_prv_is_sleep_activity(session->type)) { dls_record.sleep_data = session->sleep_data; } else { dls_record.step_data = session->step_data; } if (state->activity_dls_session == NULL) { // We don't need to be buffered since we are logging from the KernelBG task and this // saves having to allocate another buffer from the kernel heap. const bool buffered = false; const bool resume = false; Uuid system_uuid = UUID_SYSTEM; state->activity_dls_session = dls_create( DlsSystemTagActivitySession, DATA_LOGGING_BYTE_ARRAY, sizeof(dls_record), buffered, resume, &system_uuid); if (!state->activity_dls_session) { PBL_LOG(LOG_LEVEL_WARNING, "Error creating activity DLS session"); return; } } // Log the record DataLoggingResult result = dls_log(state->activity_dls_session, &dls_record, 1); if (result != DATA_LOGGING_SUCCESS) { PBL_LOG(LOG_LEVEL_WARNING, "Error %"PRIi32" while logging activity to DLS", (int32_t)result); } PBL_LOG(LOG_LEVEL_INFO, "Logging activity event %d, start_time: %"PRIu32", " "elapsed_min: %"PRIu16", end_time: %"PRIu32" ", (int)session->type, (uint32_t)session->start_utc, session->length_min, (uint32_t)session->start_utc + (session->length_min * SECONDS_PER_MINUTE)); } // This structre holds stats we collected from going through a list of sleep sessions. It is // filled in by prv_compute_sleep_stats typedef struct { ActivityScalarStore total_minutes; ActivityScalarStore restful_minutes; time_t enter_utc; // When we entered sleep time_t today_exit_utc; // last exit time for today, for regular sleep only time_t last_exit_utc; // last exit time (sleep or nap, ignoring "today" boundary) time_t last_deep_exit_utc; // last deep sleep exit time (sleep or nap, ignoring "today" boundary) uint32_t last_session_len_sec; } ActivitySleepStats; // -------------------------------------------------------------------------------------------- // Goes through a list of activity sessions and collect sleep stats // @param[in] now_utc the UTC time when the activity sessions were computed // @param[in] min_end_utc Only include sleep sessions that end AFTER this time // @param[in] max_end_utc Only include sleep sessions that end BEFORE this time // @param[in] last_processed_utc When activity sessions were computed, this is the UTC of the // most recent minute we had access to when activities were computed. // @param[out] stats this structure is filled in with the sleep stats // @return True if there were sleep session, False if not static bool prv_compute_sleep_stats(time_t now_utc, time_t min_end_utc, time_t max_end_utc, ActivitySleepStats *stats) { ActivityState *state = activity_private_state(); *stats = (ActivitySleepStats) { }; bool rv = false; // Iterate through the sleep sessions, accumulating the total sleep minutes, total // restful minutes, sleep enter time, and sleep exit time. ActivitySession *session = state->activity_sessions; for (uint32_t i = 0; i < state->activity_sessions_count; i++, session++) { // Get info on this session stats->last_session_len_sec = session->length_min * SECONDS_PER_MINUTE; time_t session_exit_utc = session->start_utc + stats->last_session_len_sec; // Skip if it ended too early if (session_exit_utc < min_end_utc) { continue; } if ((session->type == ActivitySessionType_Sleep) || (session->type == ActivitySessionType_Nap)) { rv = true; // Accumulate sleep container stats if (session_exit_utc <= max_end_utc) { stats->total_minutes += session->length_min; } // Only regular sleep (not naps) should affect the enter and exit times if (session->type == ActivitySessionType_Sleep) { stats->enter_utc = (stats->enter_utc != 0) ? MIN(session->start_utc, stats->enter_utc) : session->start_utc; if ((session_exit_utc > stats->today_exit_utc) && (session_exit_utc <= max_end_utc)) { stats->today_exit_utc = session_exit_utc; } } stats->last_exit_utc = MAX(session_exit_utc, stats->last_exit_utc); } else if ((session->type == ActivitySessionType_RestfulSleep) || (session->type == ActivitySessionType_RestfulNap)) { if (session_exit_utc <= max_end_utc) { // Accumulate restful sleep stats stats->restful_minutes += session->length_min; } stats->last_deep_exit_utc = MAX(stats->last_deep_exit_utc, session_exit_utc); } } return rv; } // -------------------------------------------------------------------------------------------- // Goes through a list of activity sessions and updates our sleep totals in the metrics // accordingly. We also take this opportunity to post a sleep metric changed event for the SDK // if the sleep totals have changed. // @param num_sessions the number of sessions in the sessions array // @param sessions array of activity sessions // @param now_utc the UTC time when the activity sessions were computed // @param max_end_utc Only include sleep sessions that end BEFORE this time // @param last_processed_utc When activity sessions were computed, this is the UTC of the // most recent minute we had access to when activities were computed. static void prv_update_sleep_metrics(time_t now_utc, time_t max_end_utc, time_t last_processed_utc) { ActivityState *state = activity_private_state(); mutex_lock_recursive(state->mutex); { // We will be filling in this structure based on the sleep sessions ActivitySleepData *sleep_data = &state->sleep_data; // If we detect a change in the sleep metrics, we want to post a health event ActivitySleepData prev_sleep_data = *sleep_data; // Collect stats on sleep ActivitySleepStats stats; if (!prv_compute_sleep_stats(now_utc, 0 /*min_end_utc*/, max_end_utc, &stats)) { // We didn't have any sleep data exit early goto unlock; } // Update our sleep metrics sleep_data->total_minutes = stats.total_minutes; sleep_data->restful_minutes = stats.restful_minutes; // Fill in the enter and exit minute uint16_t enter_minute = time_util_get_minute_of_day(stats.enter_utc); uint16_t exit_minute = time_util_get_minute_of_day(stats.today_exit_utc); sleep_data->enter_at_minute = enter_minute; sleep_data->exit_at_minute = exit_minute; // Fill in the rest of the sleep data metrics: the current state, and how long we have been // in the current state uint32_t delta_min = abs((int32_t)(last_processed_utc - stats.last_exit_utc)) / SECONDS_PER_MINUTE; // Figure out our current state if (delta_min > 1) { // We are awake sleep_data->cur_state = ActivitySleepStateAwake; if (stats.last_exit_utc != 0) { sleep_data->cur_state_elapsed_minutes = (now_utc - stats.last_exit_utc) / SECONDS_PER_MINUTE; } else { sleep_data->cur_state_elapsed_minutes = MINUTES_PER_DAY; } } else { // We are still sleeping if (stats.last_deep_exit_utc == stats.last_exit_utc) { sleep_data->cur_state = ActivitySleepStateRestfulSleep; } else { sleep_data->cur_state = ActivitySleepStateLightSleep; } sleep_data->cur_state_elapsed_minutes = (stats.last_session_len_sec + now_utc - stats.last_exit_utc) / SECONDS_PER_MINUTE; } // If the info that is part of a health sleep event has changed, send out a notification event if ((sleep_data->total_minutes != prev_sleep_data.total_minutes) || (sleep_data->restful_minutes != prev_sleep_data.restful_minutes)) { // Post a sleep changed event PebbleEvent e = { .type = PEBBLE_HEALTH_SERVICE_EVENT, .health_event = { .type = HealthEventSleepUpdate, .data.sleep_update = { .total_seconds = sleep_data->total_minutes * SECONDS_PER_MINUTE, .total_restful_seconds = sleep_data->restful_minutes * SECONDS_PER_MINUTE, }, }, }; event_put(&e); } if (sleep_data->cur_state != prev_sleep_data.cur_state) { // Debug logging ACTIVITY_LOG_DEBUG("total_min: %"PRIu16", deep_min: %"PRIu16", state: %"PRIu16", " "state_min: %"PRIu16"", sleep_data->total_minutes, sleep_data->restful_minutes, sleep_data->cur_state, sleep_data->cur_state_elapsed_minutes); } } unlock: mutex_unlock_recursive(state->mutex); } // -------------------------------------------------------------------------------------------- void activity_sessions_prv_get_sleep_bounds_utc(time_t now_utc, time_t *enter_utc, time_t *exit_utc) { // Get useful UTC times time_t start_of_today_utc = time_util_get_midnight_of(now_utc); int minute_of_day = time_util_get_minute_of_day(now_utc); int last_sleep_second_of_day = ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY * SECONDS_PER_MINUTE; int first_sleep_utc; if (minute_of_day < ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY) { // It is before the ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY (currently 9pm) cutoff, so use // the previou day's cutoff first_sleep_utc = start_of_today_utc - (SECONDS_PER_DAY - last_sleep_second_of_day); } else { // It is after 9pm, so use the 9pm cutoff first_sleep_utc = start_of_today_utc + last_sleep_second_of_day; } // Compute stats for today ActivitySleepStats stats; prv_compute_sleep_stats(now_utc, first_sleep_utc /*min_utc*/, now_utc /*max_utc*/, &stats); *enter_utc = stats.enter_utc; *exit_utc = stats.today_exit_utc; } // -------------------------------------------------------------------------------------------- // Goes through a list of activity sessions and logs new ones to data logging static void prv_log_activities(time_t now_utc) { ActivityState *state = activity_private_state(); // Activity classes. All of the activities in a class share the same "_exit_at_utc" state in // the globals and the same settings key to persist it. enum { // for ActivitySessionType_Sleep, ActivitySessionType_Nap ActivityClass_Sleep = 0, // for ActivitySessionType_RestfulSleep, ActivitySessionType_RestfulNap ActivityClass_RestfulSleep = 1, // for ActivitySessionType_Walk, ActivitySessionType_Run, ActivitySessionType_Open ActivityClass_Step = 2, // Leave at end ActivityClassCount, }; // List of event classes and info on each typedef struct { ActivitySettingsKey key; // settings key used to store last UTC time for this activity class time_t *exit_utc; // pointer to last UTC time in our globals bool modified; // true if we need to update it. } ActivityClassParams; ActivityClassParams class_settings[ActivityClassCount] = { {ActivitySettingsKeyLastSleepActivityUTC, &state->logged_sleep_activity_exit_at_utc, false}, {ActivitySettingsKeyLastRestfulSleepActivityUTC, &state->logged_restful_sleep_activity_exit_at_utc, false}, {ActivitySettingsKeyLastStepActivityUTC, &state->logged_step_activity_exit_at_utc, false}, }; bool logged_event = false; ActivitySession *session = state->activity_sessions; for (uint32_t i = 0; i < state->activity_sessions_count; i++, session++) { // Get info on this activity uint32_t session_len_sec = session->length_min * SECONDS_PER_MINUTE; time_t session_exit_utc = session->start_utc + session_len_sec; ActivityClassParams *params = NULL; switch (session->type) { case ActivitySessionType_Sleep: case ActivitySessionType_Nap: params = &class_settings[ActivityClass_Sleep]; break; case ActivitySessionType_RestfulSleep: case ActivitySessionType_RestfulNap: params = &class_settings[ActivityClass_RestfulSleep]; break; case ActivitySessionType_Walk: case ActivitySessionType_Run: case ActivitySessionType_Open: params = &class_settings[ActivityClass_Step]; break; case ActivitySessionType_None: case ActivitySessionTypeCount: WTF; break; } PBL_ASSERTN(params); // If this is an event we already logged, or it's still onging, don't log it if (session->ongoing || (session_exit_utc <= *params->exit_utc)) { continue; } // Don't log *any* sleep events until we know for sure we are awake. For restful sessions // in particular, even if the session ended, it might later be converted to a restful nap // session (after the container sleep session it is in finally ends). if (activity_sessions_prv_is_sleep_activity(session->type)) { if (state->sleep_data.cur_state != ActivitySleepStateAwake) { continue; } } // Log this event activity_sessions_prv_send_activity_session_to_data_logging(session); *params->exit_utc = session_exit_utc; params->modified = true; logged_event = true; } // Update settings file if any events were logged if (logged_event) { mutex_lock_recursive(state->mutex); SettingsFile *file = activity_private_settings_open(); if (file) { for (int i = 0; i < ActivityClassCount; i++) { ActivityClassParams *params = &class_settings[i]; status_t result = settings_file_set(file, ¶ms->key, sizeof(params->key), params->exit_utc, sizeof(*params->exit_utc)); if (result != S_SUCCESS) { PBL_LOG(LOG_LEVEL_ERROR, "Error saving last event time"); } } activity_private_settings_close(file); } mutex_unlock_recursive(state->mutex); } } // ------------------------------------------------------------------------------------------------ // Load in the stored activities from our settings file void activity_sessions_prv_init(SettingsFile *file, time_t utc_now) { ActivityState *state = activity_private_state(); ActivitySettingsKey key = ActivitySettingsKeyStoredActivities; // Check the length first. The settings_file_get() call will not return an error if we ask // for less than the value size int stored_len = settings_file_get_len(file, &key, sizeof(key)); if (stored_len != sizeof(state->activity_sessions)) { PBL_LOG(LOG_LEVEL_WARNING, "Stored activities not found or incompatible"); return; } // Read in the stored activities status_t result = settings_file_get(file, &key, sizeof(key), state->activity_sessions, sizeof(state->activity_sessions)); if (result != S_SUCCESS) { return; } // Scan to see how many valid activities we have. ActivitySession *session = state->activity_sessions; ActivitySession null_session = {}; for (unsigned i = 0; i < ARRAY_LENGTH(state->activity_sessions); i++, session++) { if (!memcmp(session, &null_session, sizeof(null_session))) { // Empty session detected, we are done break; } if (!prv_is_valid_activity_session(session)) { // NOTE: We check for full validity as well as we can (rather than just checking for a // non-null activity start time for example) because there have been cases where // flash got corrupted, as in PBL-37848 PBL_HEXDUMP(LOG_LEVEL_INFO, (void *)state->activity_sessions, sizeof(state->activity_sessions)); PBL_LOG(LOG_LEVEL_ERROR, "Invalid activity session detected - could be flash corrruption"); // Zero out flash so that we don't get into a reboot loop memset(state->activity_sessions, 0, sizeof(state->activity_sessions)); settings_file_set(file, &key, sizeof(key), state->activity_sessions, sizeof(state->activity_sessions)); WTF; } state->activity_sessions_count++; } // Remove any activities that don't belong to "today" or that are ongoing activity_sessions_prv_remove_out_of_range_activity_sessions(utc_now, true /*remove_ongoing*/); PBL_LOG(LOG_LEVEL_INFO, "Restored %"PRIu16" activities from storage", state->activity_sessions_count); } // -------------------------------------------------------------------------------------- void NOINLINE activity_sessions_prv_minute_handler(time_t utc_sec) { ActivityState *state = activity_private_state(); time_t last_sleep_processed_utc = activity_algorithm_get_last_sleep_utc(); // Post process sleep sessions if we got any new sleep sessions that showed up if (state->sleep_sessions_modified) { // Post-process the sleep activities. This is where we relabel sleep sessions as nap // sessions, depending on time and length heuristics. activity_algorithm_post_process_sleep_sessions(state->activity_sessions_count, state->activity_sessions); state->sleep_sessions_modified = false; } // Update sleep metrics // For today's metrics, we include sleep sessions that end between // ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY the previous day and ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY // today. activity_algorithm_get_activity_sessions() insures that we only get sessions // that end after ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY the previous day, so we just need to insure // that the end BEFORE ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY today. int last_sleep_utc_of_day = time_util_get_midnight_of(utc_sec) + ACTIVITY_LAST_SLEEP_MINUTE_OF_DAY * SECONDS_PER_MINUTE; prv_update_sleep_metrics(utc_sec, last_sleep_utc_of_day, last_sleep_processed_utc); // Log any new activities we detected to the phone prv_log_activities(utc_sec); } // ------------------------------------------------------------------------------------------------ bool activity_sessions_is_session_type_ongoing(ActivitySessionType type) { ActivityState *state = activity_private_state(); bool rv = false; mutex_lock_recursive(state->mutex); { for (int i = 0; i < state->activity_sessions_count; i++) { const ActivitySession *session = &state->activity_sessions[i]; if (session->type == type && session->ongoing) { rv = true; break; } } } mutex_unlock_recursive(state->mutex); return rv; } // ------------------------------------------------------------------------------------------------ DEFINE_SYSCALL(bool, sys_activity_sessions_is_session_type_ongoing, ActivitySessionType type) { return activity_sessions_is_session_type_ongoing(type); }