pebble/src/fw/services/normal/wakeup.c
2025-01-27 11:38:16 -08:00

683 lines
23 KiB
C

/*
* 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 "wakeup.h"
#include "popups/wakeup_ui.h"
#include "os/mutex.h"
#include "process_management/app_install_manager.h"
#include "process_management/app_manager.h"
#include "process_management/app_storage.h"
#include "services/common/clock.h"
#include "services/common/event_service.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/system_task.h"
#include "services/normal/settings/settings_file.h"
#include "syscall/syscall.h"
#include "syscall/syscall_internal.h"
#include "system/logging.h"
#include "util/attributes.h"
#include "util/math.h"
#include "util/units.h"
#include "kernel/pbl_malloc.h"
#define SETTINGS_FILE_NAME "wakeup"
// settings file => 29 bytes * 30 apps * 8 wakeup events = ~7000 bytes
// This should be more than enough space to store all the wakeup events we will ever want.
#define SETTINGS_FILE_SIZE KiBYTES(8)
// This represents the size of the buffer that is allocated to pass into the wakeup_ui
// to show that an app's wakeup event had triggered while the watch was off. To reduce
// complexity, I have hard coded this buffer to a max size instead of going the linked_list
// route. 16 apps should be more than enough.
#define NUM_APPS_ALERT_ON_BOOT 16
static PebbleMutex *s_mutex;
//! Settings entries == WakeupId are stored by timestamp,
//! duplicate timestamps not allowed (can't have 2 wakeup events at same time)
//! repeating and repeat_hours_offset were included for future use
//! and use in repeat support for alarms
typedef struct PACKED {
Uuid uuid; //!< UUID of app that scheduled the wakeup event
int32_t reason; //!< App provided value to differentiate wakeup event
bool repeating; //!< Enable event repetition
uint16_t repeat_hours_offset; //!< repeat hour interval
bool notify_if_missed; //!< Notify user if wakeup event has been missed
time_t timestamp; //!< The time at which this entry will wake up at
bool utc; //!< If timezone has been set, the this is UTC time
} WakeupEntry;
typedef struct PACKED {
WakeupId current_wakeup_id;
WakeupId next_wakeup_id;
time_t timestamp;
} WakeupState;
struct prv_missed_events_s {
uint8_t missed_apps_count;
AppInstallId *missed_app_ids;
};
struct prv_check_app_and_wakeup_event_s {
time_t wakeup_timestamp; //!< Timestamp of the WakupEntry
int wakeup_count; //!< wakeup event count for app, negative for error (StatusCode)
};
// Local prototypes
static WakeupEntry prv_wakeup_settings_get_entry(WakeupId wakeup_id);
static void prv_wakeup_settings_delete_entry(WakeupId wakeup_id);
static StatusCode prv_wakeup_settings_add_entry(WakeupId wakeup_id, WakeupEntry entry);
static void prv_wakeup_timer_next_pending(void);
static bool s_wakeup_enabled = false;
static TimerID s_current_timer_id = TIMER_INVALID_ID; // single timer reused for each event
// single structure containing the global wakeup state
static WakeupState s_wakeup_state = { -1, -1, 0 };
static bool s_catchup_enabled = false; // enables catching up with missed events
void wakeup_dispatcher_system_task(void *data){
WakeupId wakeup_id = (WakeupId)data;
WakeupEntry entry = prv_wakeup_settings_get_entry(wakeup_id);
// Delete event from settings
prv_wakeup_settings_delete_entry(wakeup_id);
AppInstallId app_id = app_install_get_id_for_uuid(&entry.uuid);
// If specified app isn't currently running, launch
if (!(app_manager_get_current_app_id() == app_id)) {
// Lookup app, and if installed, launch
if (app_id != INSTALL_ID_INVALID) {
PebbleLaunchAppEventExtended* data =
kernel_malloc_check(sizeof(PebbleLaunchAppEventExtended));
*data = (PebbleLaunchAppEventExtended) {
.common.reason = APP_LAUNCH_WAKEUP,
.wakeup.wakeup_id = wakeup_id,
.wakeup.wakeup_reason = entry.reason,
};
data->common.args = &data->wakeup;
PebbleEvent event = {
.type = PEBBLE_APP_LAUNCH_EVENT,
.launch_app = {
.id = app_id,
.data = data
}
};
event_put(&event);
}
} else {
// If app running, send event
PebbleEvent event = {
.type = PEBBLE_WAKEUP_EVENT,
.wakeup = {
.wakeup_info = {
.wakeup_id = wakeup_id,
.wakeup_reason = entry.reason
}
}
};
event_put(&event);
}
prv_wakeup_timer_next_pending();
}
// This callback is the system dispatcher that wakes up the application by AppInstallId
// OR queues a wakeup callback for that application
// and is triggered by NewTimer
static void prv_wakeup_dispatcher(void *data) {
// Place actual work on system_task to unload it from new_timer task
system_task_add_callback(wakeup_dispatcher_system_task, data);
}
static bool prv_find_next_wakeup_id_callback(SettingsFile *file,
SettingsRecordInfo *info, void *context) {
// Check if valid entry
if (info->key_len != sizeof(WakeupId) || info->val_len != sizeof(WakeupEntry)) {
return true; // continue iterating
}
WakeupId wakeup_id;
info->get_key(file, (uint8_t*)&wakeup_id, sizeof(WakeupId));
WakeupEntry entry;
info->get_val(file, (uint8_t*)&entry, sizeof(WakeupEntry));
// If the wakeup_id is valid, and the timestamp of the entry is closer than
// the timestamp of our global wakeup state, then set the next close wakeup event
if (wakeup_id > 0 && (s_wakeup_state.current_wakeup_id == -1 ||
entry.timestamp < s_wakeup_state.timestamp)) {
s_wakeup_state.timestamp = entry.timestamp;
s_wakeup_state.current_wakeup_id = wakeup_id;
}
return true; // continue iterating
}
// Checks for the next pending wakeup event and sets up a timer for the event
static void prv_wakeup_timer_next_pending(void) {
if (!s_wakeup_enabled) {
return;
}
// If there is already a wakeup timer scheduled, cancel it. There will be a
// new timer scheduled for the soonest wakeup that is registered.
if (new_timer_scheduled(s_current_timer_id, NULL)) {
new_timer_stop(s_current_timer_id);
}
mutex_lock(s_mutex);
{
// Find the next event to occur
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
// Reset wakeup state to use for the search
s_wakeup_state.current_wakeup_id = -1;
s_wakeup_state.timestamp = 0;
settings_file_each(&wakeup_settings, prv_find_next_wakeup_id_callback, NULL);
settings_file_close(&wakeup_settings);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Error: could not open APP_WAKEUP settings");
}
}
mutex_unlock(s_mutex);
// Create a timer for the found wakeup id, given it's valid
if (s_wakeup_state.current_wakeup_id >= 0) {
int32_t timestamp = s_wakeup_state.timestamp;
time_t current_time = rtc_get_time();
int32_t time_difference = timestamp - current_time;
// If time_difference is negative, this was a missed past event due to set_time
// changing current time beyond the event
// Note: this catches the edge case that there are several wakeup events skipped
// and avoids clobbering these events with a WAKEUP_CATCHUP_WINDOW gap,
// including an event occurring after missed events
if (time_difference < 0 || s_catchup_enabled) {
// next catchup_enabled state before modifying time_difference
s_catchup_enabled = (time_difference < 0) ? true : false;
// Enforces the catchup gap to be at least WAKEUP_CATCHUP_WINDOW gap
time_difference = MAX(time_difference, WAKEUP_CATCHUP_WINDOW);
}
// timers are in milliseconds, set main callback dispatch for wakeup
// WakeupId is used to save/restore/lookup wakeup events
new_timer_start(s_current_timer_id, (time_difference * 1000), prv_wakeup_dispatcher,
(void*)((intptr_t)s_wakeup_state.current_wakeup_id), 0);
}
}
static void prv_migrate_events_callback(SettingsFile *old_file, SettingsFile *new_file,
SettingsRecordInfo *info, void *utc_diff) {
if (!utc_diff || info->key_len != sizeof(WakeupId) || info->val_len != sizeof(WakeupEntry)) {
return;
}
WakeupId wakeup_id;
info->get_val(old_file, (uint8_t*)&wakeup_id, sizeof(WakeupId));
WakeupEntry entry;
info->get_val(old_file, (uint8_t*)&entry, sizeof(WakeupEntry));
// Migrate the entries to the new timezone
if (entry.utc == false) {
entry.timestamp -= *((int*)utc_diff);
entry.utc = true;
if (wakeup_id == s_wakeup_state.current_wakeup_id) {
s_wakeup_state.timestamp = entry.timestamp;
}
}
// Write the new entry to the settings file. We always write as there's no
// chance of it being invalid.
settings_file_set(new_file, (uint8_t*)&wakeup_id, sizeof(WakeupId),
(uint8_t*)&entry, sizeof(WakeupEntry));
}
static bool prv_check_for_events(SettingsFile *file, SettingsRecordInfo *info, void *context) {
bool *event_found = (bool *)context;
*event_found = true;
return false; // stop iterating
}
static void prv_update_events_callback(SettingsFile *old_file, SettingsFile *new_file,
SettingsRecordInfo *info, void *context) {
// Check if valid entry
if (!context || info->key_len != sizeof(WakeupId)) {
return;
}
bool struct_size_mismatch = info->val_len != sizeof(WakeupEntry);
bool struct_migration = !clock_is_timezone_set() && struct_size_mismatch;
if (struct_size_mismatch && !struct_migration) {
return;
}
struct prv_missed_events_s *missed_events = (struct prv_missed_events_s*)context;
WakeupId wakeup_id;
info->get_key(old_file, (uint8_t*)&wakeup_id, sizeof(WakeupId));
WakeupEntry entry;
info->get_val(old_file, (uint8_t*)&entry, info->val_len);
if (struct_migration) {
entry.timestamp = wakeup_id; // WakeupId (key) is a timestamp
entry.utc = false; // If we're migrating, this has not been utc
}
int32_t timestamp = entry.timestamp;
time_t current_time = rtc_get_time();
int32_t time_difference = timestamp - current_time;
if (timestamp >= s_wakeup_state.next_wakeup_id) {
s_wakeup_state.next_wakeup_id = timestamp + 1;
} else if (wakeup_id >= s_wakeup_state.next_wakeup_id) {
s_wakeup_state.next_wakeup_id = wakeup_id + 1;
}
// schedule non-expired events
if (time_difference > 0) {
// Using settings_file_rewrite, need to write to keep key/value
settings_file_set(new_file, (uint8_t*)&wakeup_id, sizeof(WakeupId),
(uint8_t*)&entry, sizeof(WakeupEntry));
} else {
if (entry.notify_if_missed) {
if (missed_events->missed_app_ids == NULL) {
// This is allocated here, but free'd in the wakup_ui.h module
missed_events->missed_app_ids =
kernel_malloc(NUM_APPS_ALERT_ON_BOOT * sizeof(AppInstallId));
}
if (missed_events->missed_apps_count > NUM_APPS_ALERT_ON_BOOT) {
// We have more than NUM_APPS_ALERT_ON_BOOT apps that had events fire while the watch
// was shut off. Very rare this will happen, but just down show that the apps > 16 missed
// an event.
return;
}
// Set the AppInstallId of the app that had an alert fired
missed_events->missed_app_ids[missed_events->missed_apps_count++] =
app_install_get_id_for_uuid(&entry.uuid);
}
// Deletes the entry automatically if not written
}
}
void wakeup_init(void) {
struct prv_missed_events_s missed_events = { 0, NULL };
s_mutex = mutex_create();
event_service_init(PEBBLE_WAKEUP_EVENT, NULL, NULL);
// Create single reusable timer for wakeup events
s_current_timer_id = new_timer_create();
s_wakeup_state.next_wakeup_id = rtc_get_time();
s_wakeup_state.timestamp = -1;
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) != S_SUCCESS) {
PBL_LOG(LOG_LEVEL_ERROR, "Error: could not open wakeup settings");
return;
}
// Want to check if there are any events first to prevent us from rewriting the file on boot if
// we don't need to.
bool event_found = false;
settings_file_each(&wakeup_settings, prv_check_for_events, &event_found);
if (event_found) {
PBL_LOG(LOG_LEVEL_DEBUG, "Rewriting wakeup file");
// Update settings file removing expired events
settings_file_rewrite(&wakeup_settings, prv_update_events_callback, &missed_events);
} else {
PBL_LOG(LOG_LEVEL_DEBUG, "Not rewriting wakeup file because no entries were found");
}
settings_file_close(&wakeup_settings);
// If wakeup events were missed by apps requesting notify_if_missed
// popup a notification window displaying these apps
if (missed_events.missed_apps_count) {
wakeup_popup_window(missed_events.missed_apps_count, missed_events.missed_app_ids);
}
}
static bool prv_compiled_without_utc_support(void) {
static const Version first_utc_version = {
// See list of changes in pebble_process_info.h. Apps compiled prior to this version will
// get local time returned from the time() call.
0x5,
0x2f
};
Version app_sdk_version = process_metadata_get_sdk_version(
sys_process_manager_get_current_process_md());
if (version_compare(app_sdk_version, first_utc_version) < 0) {
return true;
}
return false;
}
DEFINE_SYSCALL(WakeupId, sys_wakeup_schedule, time_t timestamp, int32_t reason,
bool notify_if_missed) {
if (prv_compiled_without_utc_support()) {
// Legacy apps get local time returned from the time() call.
timestamp = time_local_to_utc(timestamp);
}
time_t current_time = rtc_get_time();
int32_t time_difference = timestamp - current_time;
WakeupId wakeup_id = s_wakeup_state.next_wakeup_id++;
// Disallow scheduling past events
if (time_difference <= 0) {
return E_INVALID_ARGUMENT;
}
Uuid uuid = app_manager_get_current_app_md()->uuid;
WakeupEntry entry = {
.uuid = uuid,
.reason = reason,
.notify_if_missed = notify_if_missed,
.timestamp = timestamp,
.utc = clock_is_timezone_set()
};
// Add to settings file
StatusCode retval = prv_wakeup_settings_add_entry(wakeup_id, entry);
// If there was an error adding the wakeup event, return the error
if (retval < S_SUCCESS) {
return retval;
}
// If this new event is sooner than the currently scheduled timer, make this
// the current one
prv_wakeup_timer_next_pending();
return wakeup_id;
}
static void prv_wakeup_settings_delete_entry(WakeupId wakeup_id) {
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
settings_file_delete(&wakeup_settings, (uint8_t*)&wakeup_id, sizeof(WakeupId));
settings_file_close(&wakeup_settings);
}
}
mutex_unlock(s_mutex);
}
static WakeupEntry prv_wakeup_settings_get_entry(WakeupId wakeup_id) {
WakeupEntry entry = {{0}};
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
settings_file_get(&wakeup_settings, (uint8_t*)&wakeup_id, sizeof(WakeupId),
(uint8_t*)&entry, sizeof(WakeupEntry));
settings_file_close(&wakeup_settings);
}
}
mutex_unlock(s_mutex);
return entry;
}
DEFINE_SYSCALL(void, sys_wakeup_delete, WakeupId wakeup_id) {
WakeupEntry entry = prv_wakeup_settings_get_entry(wakeup_id);
// Only allow owner to delete its own wakeup events
if (uuid_equal(&app_manager_get_current_app_md()->uuid, &entry.uuid)) {
if (wakeup_id == s_wakeup_state.current_wakeup_id &&
new_timer_scheduled(s_current_timer_id, NULL)) {
new_timer_stop(s_current_timer_id);
}
prv_wakeup_settings_delete_entry(wakeup_id);
prv_wakeup_timer_next_pending();
}
}
static bool prv_check_count_and_availability_callback(SettingsFile *file,
SettingsRecordInfo *info, void *context) {
// Check if valid entry
if (!context || info->key_len != sizeof(WakeupId) || info->val_len != sizeof(WakeupEntry)) {
return true; // continue iterating
}
struct prv_check_app_and_wakeup_event_s *check = (struct prv_check_app_and_wakeup_event_s*)context;
WakeupId wakeup_id;
info->get_key(file, (uint8_t*)&wakeup_id, sizeof(WakeupId));
WakeupEntry entry;
info->get_val(file, (uint8_t*)&entry, sizeof(WakeupEntry));
//If we have already flagged an error, just skip the rest
if (check->wakeup_count < S_SUCCESS) {
return true; // continue iterating
}
if (uuid_equal(&app_manager_get_current_app_md()->uuid, &entry.uuid)) {
check->wakeup_count++;
}
// If the wakeup_id is with the same minute as another wakeup event
if ((entry.timestamp - WAKEUP_EVENT_WINDOW < check->wakeup_timestamp) &&
(check->wakeup_timestamp < (entry.timestamp + WAKEUP_EVENT_WINDOW))) {
check->wakeup_count = E_RANGE;
}
return true; // continue iterating
}
static StatusCode prv_wakeup_settings_add_entry(WakeupId wakeup_id, WakeupEntry entry) {
status_t status = S_SUCCESS;
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
// Check if current app already has MAX_WAKEUP_EVENTS_PER_APP scheduled
// or if the minute event window is already occupied
struct prv_check_app_and_wakeup_event_s check = {
.wakeup_count = 0,
.wakeup_timestamp = entry.timestamp
};
settings_file_each(&wakeup_settings, prv_check_count_and_availability_callback, &check);
if (check.wakeup_count < S_SUCCESS) {
status = check.wakeup_count;
} else if (check.wakeup_count >= MAX_WAKEUP_EVENTS_PER_APP) {
status = E_OUT_OF_RESOURCES;
} else {
settings_file_set(&wakeup_settings, (uint8_t*)&wakeup_id, sizeof(WakeupId),
(uint8_t*)&entry, sizeof(WakeupEntry));
}
settings_file_close(&wakeup_settings);
} else {
status = E_INTERNAL;
}
}
mutex_unlock(s_mutex);
return status;
}
static void prv_delete_events_by_uuid_callback(SettingsFile *old_file, SettingsFile *new_file,
SettingsRecordInfo *info, void *context) {
// Check if valid entry
if (info->key_len != sizeof(WakeupId) || info->val_len != sizeof(WakeupEntry)) {
return;
}
WakeupId wakeup_id;
info->get_key(old_file, (uint8_t*)&wakeup_id, sizeof(WakeupId));
WakeupEntry entry;
info->get_val(old_file, (uint8_t*)&entry, sizeof(WakeupEntry));
// If the UUID is equal, delete the entry
if (uuid_equal(&app_manager_get_current_app_md()->uuid, &entry.uuid)) {
// if this is the current timer event, cancel it
if (wakeup_id == s_wakeup_state.current_wakeup_id &&
new_timer_scheduled(s_current_timer_id, NULL)) {
new_timer_stop(s_current_timer_id);
}
// Deletes the entry automatically if not written
} else { // Keep the entry
// Using settings_file_rewrite, need to write to keep key/value
settings_file_set(new_file, (uint8_t*)&wakeup_id, sizeof(WakeupId),
(uint8_t*)&entry, sizeof(WakeupEntry));
}
}
DEFINE_SYSCALL(void, sys_wakeup_cancel_all_for_app, void) {
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
// Update settings file removing all events with UUID = uuid
settings_file_rewrite(&wakeup_settings, prv_delete_events_by_uuid_callback, NULL);
settings_file_close(&wakeup_settings);
}
}
mutex_unlock(s_mutex);
// We may have cancelled the timer, next_pending will check
prv_wakeup_timer_next_pending();
}
DEFINE_SYSCALL(time_t, sys_wakeup_query, WakeupId wakeup_id) {
status_t status = E_DOES_NOT_EXIST;
WakeupEntry entry = {};
if (wakeup_id < 0) {
return status;
}
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
// Check if the wakeup id is valid by seeing if it is in the wakeup settings_file
status = settings_file_get(&wakeup_settings, (uint8_t*)&wakeup_id, sizeof(WakeupId),
(uint8_t*)&entry, sizeof(WakeupEntry));
settings_file_close(&wakeup_settings);
} else {
status = E_INTERNAL;
}
}
mutex_unlock(s_mutex);
if (status != S_SUCCESS) {
return status;
}
// timer doesn't belong to this app
if (!uuid_equal(&app_manager_get_current_app_md()->uuid, &entry.uuid)) {
return E_DOES_NOT_EXIST;
}
time_t return_time = entry.timestamp;
if (prv_compiled_without_utc_support()) {
// Legacy apps expect everything in local time.
return_time = time_utc_to_local(return_time);
}
return return_time;
}
void wakeup_enable(bool enable) {
bool was_enabled = s_wakeup_enabled;
s_wakeup_enabled = enable;
if (enable && !was_enabled) {
prv_wakeup_timer_next_pending();
} else if (!enable && s_current_timer_id &&
new_timer_scheduled(s_current_timer_id, NULL)) {
new_timer_stop(s_current_timer_id);
}
}
TimerID wakeup_get_current(void) {
return s_current_timer_id;
}
WakeupId wakeup_get_next_scheduled(void) {
return s_wakeup_state.current_wakeup_id;
}
void wakeup_migrate_timezone(int utc_diff) {
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
settings_file_rewrite(&wakeup_settings, prv_migrate_events_callback, (void*)&utc_diff);
settings_file_close(&wakeup_settings);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Error: could not open wakeup settings");
}
}
mutex_unlock(s_mutex);
}
static void prv_wakeup_rewrite_kernel_bg_cb(void *data) {
// Update each wakeup entry via prv_update_events_callback and record any missed events
struct prv_missed_events_s missed_events = { 0, NULL };
mutex_lock(s_mutex);
{
SettingsFile wakeup_settings;
if (settings_file_open(&wakeup_settings, SETTINGS_FILE_NAME, SETTINGS_FILE_SIZE) == S_SUCCESS) {
// Update each wakeup entry via prv_update_events_callback and record any missed events
settings_file_rewrite(&wakeup_settings, prv_update_events_callback, &missed_events);
settings_file_close(&wakeup_settings);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Error: could not open wakeup settings");
}
}
mutex_unlock(s_mutex);
// If any events were missed due to time change display the wakeup popup
if (missed_events.missed_apps_count) {
wakeup_popup_window(missed_events.missed_apps_count, missed_events.missed_app_ids);
}
// Setup a timer for the next wakeup event; will return if wakeup is not enabled
prv_wakeup_timer_next_pending();
}
void wakeup_handle_clock_change(void) {
// Offload the rewrite of the wakeup file to KernelBG as it may take a while
//
// TODO: The flash burden of this routine could also be reduced by not doing
// rewrites and instead updating records in place
if (pebble_task_get_current() == PebbleTask_KernelBackground) {
prv_wakeup_rewrite_kernel_bg_cb(NULL);
} else {
system_task_add_callback(prv_wakeup_rewrite_kernel_bg_cb, NULL);
}
}