mirror of
https://github.com/google/pebble.git
synced 2025-05-05 09:21:40 -04:00
683 lines
23 KiB
C
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);
|
|
}
|
|
}
|