/* * 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 "shell/normal/quick_launch.h" #include "shell/normal/watchface.h" #include "shell/prefs.h" #include "shell/prefs_private.h" #include "shell/system_theme.h" #include "apps/system_apps/toggle/quiet_time.h" #include "board/board.h" #include "drivers/backlight.h" #include "mfg/mfg_info.h" #include "os/mutex.h" #include "popups/timeline/peek.h" #include "process_management/app_install_manager.h" #include "process_management/process_manager.h" #include "services/common/hrm/hrm_manager.h" #include "services/common/i18n/i18n.h" #include "services/normal/bluetooth/ble_hrm.h" #include "services/normal/settings/settings_file.h" #include "services/normal/timeline/peek.h" #include "system/logging.h" #include "system/passert.h" #include "util/size.h" #include "util/uuid.h" #if CAPABILITY_HAS_HEALTH_TRACKING #include "services/normal/activity/activity.h" #include "services/normal/activity/activity_insights.h" #endif #include static PebbleMutex *s_mutex; #define PREF_KEY_CLOCK_24H "clock24h" static bool s_clock_24h = false; #define PREF_KEY_CLOCK_TIMEZONE_SOURCE_IS_MANUAL "timezoneSource" static bool s_clock_timezone_source_is_manual = false; #define PREF_KEY_CLOCK_PHONE_TIMEZONE_ID "automaticTimezoneID" static int16_t s_clock_phone_timezone_id = -1; #define PREF_KEY_UNITS_DISTANCE "unitsDistance" static uint8_t s_units_distance = UnitsDistance_Miles; #define PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED "lightBehaviour" #define PREF_KEY_BACKLIGHT_ENABLED "lightEnabled" static bool s_backlight_enabled = true; #define PREF_KEY_BACKLIGHT_AMBIENT_SENSOR_ENABLED "lightAmbientSensorEnabled" static bool s_backlight_ambient_sensor_enabled = true; #define PREF_KEY_BACKLIGHT_TIMEOUT_MS "lightTimeoutMs" static uint32_t s_backlight_timeout_ms = DEFAULT_BACKLIGHT_TIMEOUT_MS; #define PREF_KEY_BACKLIGHT_INTENSITY "lightIntensity" static uint16_t s_backlight_intensity; // default pulled from BOARD_CONFIGs in shell_prefs_init() #define PREF_KEY_BACKLIGHT_MOTION "lightMotion" static bool s_backlight_motion_enabled = true; #define PREF_KEY_STATIONARY "stationaryMode" #if RELEASE && !PLATFORM_SPALDING static bool s_stationary_mode_enabled = false; #else static bool s_stationary_mode_enabled = true; #endif #define PREF_KEY_DEFAULT_WORKER "workerId" static Uuid s_default_worker = UUID_INVALID_INIT; // We use "textStyle" to indicate the content size #define PREF_KEY_TEXT_STYLE "textStyle" static uint8_t s_text_style = PreferredContentSizeDefault; #if !UNITTEST _Static_assert(sizeof(PreferredContentSize) == sizeof(s_text_style), "sizeof(PreferredContentSize) grew, pref needs to be migrated!"); #endif #define PREF_KEY_LANG_ENGLISH "langEnglish" static bool s_language_english = false; typedef struct QuickLaunchPreference { bool enabled; Uuid uuid; } QuickLaunchPreference; #define PREF_KEY_QUICK_LAUNCH_UP "qlUp" #define PREF_KEY_QUICK_LAUNCH_DOWN "qlDown" #define PREF_KEY_QUICK_LAUNCH_SELECT "qlSelect" #define PREF_KEY_QUICK_LAUNCH_BACK "qlBack" static QuickLaunchPreference s_quick_launch_up = { .enabled = true, .uuid = UUID_INVALID_INIT, }; static QuickLaunchPreference s_quick_launch_down = { .enabled = true, .uuid = UUID_INVALID_INIT, }; static QuickLaunchPreference s_quick_launch_select = { .enabled = true, .uuid = UUID_INVALID_INIT, }; static QuickLaunchPreference s_quick_launch_back = { .enabled = true, .uuid = QUIET_TIME_TOGGLE_UUID, }; #define PREF_KEY_QUICK_LAUNCH_SETUP_OPENED "qlSetupOpened" static uint8_t s_quick_launch_setup_opened = 0; #define PREF_KEY_DEFAULT_WATCHFACE "watchface" static Uuid s_default_watchface = UUID_INVALID_INIT; #define PREF_KEY_WELCOME_VERSION "welcomeVersion" static uint8_t s_welcome_version = 0; #if CAPABILITY_HAS_HEALTH_TRACKING #define PREF_KEY_ACTIVITY_PREFERENCES "activityPreferences" static ActivitySettings s_activity_preferences = ACTIVITY_DEFAULT_PREFERENCES; #define PREF_KEY_ACTIVITY_ACTIVATED_TIMESTAMP "activityActivated" static time_t s_activity_activation_timestamp = 0; #define PREF_KEY_ACTIVITY_ACTIVATION_DELAY_INSIGHT "activityActivationDelayInsights" static uint32_t s_activity_activation_delay_insight = 0; #define PREF_KEY_ACTIVITY_HEALTH_APP_OPENED "activityHealthAppOpened" static uint8_t s_activity_prefs_health_app_opened = 0; #define PREF_KEY_ACTIVITY_WORKOUT_APP_OPENED "activityWorkoutAppOpened" static uint8_t s_activity_prefs_workout_app_opened = 0; #define PREF_KEY_ALARMS_APP_OPENED "alarmsAppOpened" static uint8_t s_alarms_app_opened = 0; #define PREF_KEY_ACTIVITY_HRM_PREFERENCES "hrmPreferences" static ActivityHRMSettings s_activity_hrm_preferences = ACTIVITY_HRM_DEFAULT_PREFERENCES; #define PREF_KEY_ACTIVITY_HEART_RATE_PREFERENCES "heartRatePreferences" static HeartRatePreferences s_activity_hr_preferences = ACTIVITY_HEART_RATE_DEFAULT_PREFERENCES; #endif // CAPABILITY_HAS_HEALTH_TRACKING #if PLATFORM_SPALDING #define PREF_KEY_DISPLAY_USER_OFFSET "displayUserOffset" static GPoint s_display_user_offset = {0, 0}; #define PREF_KEY_SHOULD_PROMPT_DISPLAY_CALIBRATION "promptDisplayCal" static bool s_should_prompt_display_calibration = true; #endif #if CAPABILITY_HAS_TIMELINE_PEEK #define PREF_KEY_TIMELINE_SETTINGS_OPENED "timelineSettingsOpened" static uint8_t s_timeline_settings_opened = 0; #define PREF_KEY_TIMELINE_PEEK_ENABLED "timelineQuickViewEnabled" static bool s_timeline_peek_enabled = true; #define PREF_KEY_TIMELINE_PEEK_BEFORE_TIME_M "timelineQuickViewBeforeTimeMin" static uint16_t s_timeline_peek_before_time_m = (TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S / SECONDS_PER_MINUTE); #endif // ============================================================================================ // Handlers for each pref that validate the new setting and store the new value in our globals. // This handler will be called when the setting is changed from inside the firmware using one of // the "set" calls or when a pref is changed via a blob_db insert operation from the mobile // (after we receive the blob_db update event). // // If changing of the setting requires more than just setting a global, this handler is the // place to perform those other actions. // // If the the handler gets passed a invalid new value, set its s_* global to a default value, // and return false. This will trigger a rewrite of the s_* global to the backing file. // // Each of these functions MUST be named using the following pattern because they are called // programmatically via the PREFS_MACRO macro defined below: // static bool prv_set_( new_value) static bool prv_set_s_clock_24h(bool *new_value) { s_clock_24h = *new_value; return true; } static bool prv_set_s_clock_timezone_source_is_manual(bool *new_value) { s_clock_timezone_source_is_manual = *new_value; return true; } static bool prv_set_s_clock_phone_timezone_id(int16_t *new_value) { s_clock_phone_timezone_id = *new_value; return true; } static bool prv_set_s_units_distance(uint8_t *new_unit) { if (*new_unit >= UnitsDistanceCount) { s_units_distance = UnitsDistance_Miles; return false; } s_units_distance = *new_unit; return true; }; static bool prv_set_s_backlight_enabled(bool *enabled) { s_backlight_enabled = *enabled; return true; } static bool prv_set_s_backlight_ambient_sensor_enabled(bool *enabled) { s_backlight_ambient_sensor_enabled = *enabled; return true; } static bool prv_set_s_backlight_timeout_ms(uint32_t *timeout_ms) { if (*timeout_ms > 0) { s_backlight_timeout_ms = *timeout_ms; return true; } s_backlight_timeout_ms = DEFAULT_BACKLIGHT_TIMEOUT_MS; return false; } static uint16_t prv_convert_backlight_percent_to_intensity(uint32_t percent_intensity); static bool prv_set_s_backlight_intensity(uint16_t *intensity) { if (*intensity > BACKLIGHT_BRIGHTNESS_OFF) { s_backlight_intensity = *intensity; return true; } s_backlight_intensity = prv_convert_backlight_percent_to_intensity(BOARD_CONFIG.backlight_on_percent); return false; } static bool prv_set_s_backlight_motion_enabled(bool *enabled) { s_backlight_motion_enabled = *enabled; return true; } static bool prv_set_s_stationary_mode_enabled(bool *enabled) { s_stationary_mode_enabled = *enabled; return true; } static bool prv_set_s_default_worker(Uuid *uuid) { s_default_worker = *uuid; return true; } static bool prv_set_s_text_style(uint8_t *style) { s_text_style = *style; return true; } static bool prv_set_s_language_english(bool *english) { s_language_english = *english; i18n_enable(!s_language_english); return true; } static bool prv_set_s_quick_launch_up(QuickLaunchPreference *pref) { s_quick_launch_up = *pref; return true; } static bool prv_set_s_quick_launch_down(QuickLaunchPreference *pref) { s_quick_launch_down = *pref; return true; } static bool prv_set_s_quick_launch_select(QuickLaunchPreference *pref) { s_quick_launch_select = *pref; return true; } static bool prv_set_s_quick_launch_back(QuickLaunchPreference *pref) { s_quick_launch_back = *pref; return true; } static bool prv_set_s_quick_launch_setup_opened(uint8_t *version) { s_quick_launch_setup_opened = *version; return true; } static bool prv_set_s_default_watchface(Uuid *uuid) { s_default_watchface = *uuid; return true; } static bool prv_set_s_welcome_version(uint8_t *version) { s_welcome_version = *version; return true; } #if CAPABILITY_HAS_HEALTH_TRACKING static bool prv_set_s_activity_preferences(ActivitySettings *new_settings) { bool invalid_data = false; if (new_settings->height_mm <= 0) { new_settings->height_mm = ACTIVITY_DEFAULT_HEIGHT_MM; invalid_data = true; } if (new_settings->weight_dag <= 0) { new_settings->weight_dag = ACTIVITY_DEFAULT_WEIGHT_DAG; invalid_data = true; } if (!(new_settings->gender == ActivityGenderMale || new_settings->gender == ActivityGenderFemale || new_settings->gender == ActivityGenderOther)) { new_settings->gender = ACTIVITY_DEFAULT_GENDER; invalid_data = true; } if (new_settings->age_years <= 0) { new_settings->age_years = ACTIVITY_DEFAULT_AGE_YEARS; invalid_data = true; } if (new_settings->tracking_enabled) { activity_start_tracking(false); } else { activity_stop_tracking(); } s_activity_preferences = *new_settings; // If we received invalid data, we return false, so that prefs_private_handle_blob_db_event // will rewrite s_activity_preferences to the backing file return !invalid_data; } static bool prv_set_s_activity_activation_timestamp(time_t *timestamp) { s_activity_activation_timestamp = *timestamp; return true; } static bool prv_set_s_activity_activation_delay_insight(uint32_t *insight_bitmask) { s_activity_activation_delay_insight = *insight_bitmask; return true; } static bool prv_set_s_activity_prefs_health_app_opened(uint8_t *version) { s_activity_prefs_health_app_opened = *version; return true; } static bool prv_set_s_activity_prefs_workout_app_opened(uint8_t *version) { s_activity_prefs_workout_app_opened = *version; return true; } static bool prv_set_s_alarms_app_opened(uint8_t *version) { s_alarms_app_opened = *version; return true; } static bool prv_set_s_activity_hr_preferences(HeartRatePreferences *new_settings) { if (new_settings->resting_hr > new_settings->elevated_hr || new_settings->elevated_hr > new_settings->max_hr) { return false; } if (new_settings->zone1_threshold > new_settings->zone2_threshold || new_settings->zone2_threshold > new_settings->zone3_threshold) { return false; } s_activity_hr_preferences = *new_settings; return true; } static bool prv_set_s_activity_hrm_preferences(ActivityHRMSettings *new_settings) { // Set the preference before calling `hrm_manager_enable` because it actually queries // for the setting s_activity_hrm_preferences = *new_settings; #if CAPABILITY_HAS_BUILTIN_HRM hrm_manager_handle_prefs_changed(); #endif // CAPABILITY_HAS_BUILTIN_HRM #if BLE_HRM_SERVICE ble_hrm_handle_activity_prefs_heart_rate_is_enabled(new_settings->enabled); #endif // BLE_HRM_SERVICE return true; } #endif /* CAPABILITY_HAS_HEALTH_TRACKING */ #if PLATFORM_SPALDING static bool prv_set_s_display_user_offset(GPoint *offset) { s_display_user_offset = *offset; return true; } static bool prv_set_s_should_prompt_display_calibration(bool should_prompt) { s_should_prompt_display_calibration = should_prompt; return true; } #endif #if CAPABILITY_HAS_TIMELINE_PEEK static uint8_t prv_set_s_timeline_settings_opened(uint8_t *version) { s_timeline_settings_opened = *version; return true; } static bool prv_set_s_timeline_peek_enabled(bool *enabled) { s_timeline_peek_enabled = *enabled; timeline_peek_set_enabled(*enabled); return true; } static bool prv_set_s_timeline_peek_before_time_m(uint16_t *before_time_m) { s_timeline_peek_before_time_m = *before_time_m; timeline_peek_set_show_before_time(*before_time_m * SECONDS_PER_MINUTE); return true; } #endif // ------------------------------------------------------------------------------------ // Table of all prefs typedef bool (*PrefSetHandler)(const void *value, size_t val_len); typedef struct { const char *key; void *value; uint16_t value_len; PrefSetHandler handler; } PrefsTableEntry; // The PREFS_DECLARE_HANDLER creates a springboard function with a generic signature // (of type PrefSetHandler) which simply calls into the specialized function prv_set_ // after dereferencing the void* argument using the right type for that pref #define PREFS_MACRO(name, var) \ static bool prv_set_ ## var ## _cb(const void *value, size_t val_len) { \ return prv_set_ ## var ((__typeof__(var) *)value); \ } #include "prefs_values.h.inc" #undef PREFS_MACRO // Create a time containing the key name and global variable name for each pref #define PREFS_MACRO(key, var) {key, &var, sizeof(var), prv_set_ ## var ## _cb}, static const PrefsTableEntry s_prefs_table[] = { #include "prefs_values.h.inc" }; #undef PREFS_MACRO // ------------------------------------------------------------------------------------ // FIXME PBL-22272. We back convert this value in // settings_display.c:prv_get_scaled_brightness() We should really just store // the percent intensity or a setting level name and leave it up to the light // module to do the conversion static uint16_t prv_convert_backlight_percent_to_intensity(uint32_t percent_intensity) { return (BACKLIGHT_BRIGHTNESS_MAX * (uint32_t)percent_intensity) / 100; } // ------------------------------------------------------------------------------------ static void prv_convert_deprecated_backlight_behaviour_key(SettingsFile *file) { // if present, convert deprecated BACKLIGHT_BEHAVIOUR key to the two new separate keys if (settings_file_exists(file, PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED, sizeof(PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED))) { bool temp; BacklightBehaviour backlight_behaviour = BacklightBehaviour_Auto; settings_file_get(file, PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED, sizeof(PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED), &backlight_behaviour, sizeof(backlight_behaviour)); temp = (backlight_behaviour != BacklightBehaviour_Off); settings_file_set(file, PREF_KEY_BACKLIGHT_ENABLED, sizeof(PREF_KEY_BACKLIGHT_ENABLED), &temp, sizeof(temp)); temp = (backlight_behaviour != BacklightBehaviour_On); settings_file_set(file, PREF_KEY_BACKLIGHT_AMBIENT_SENSOR_ENABLED, sizeof(PREF_KEY_BACKLIGHT_AMBIENT_SENSOR_ENABLED), &temp, sizeof(temp)); settings_file_delete(file, PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED, sizeof(PREF_KEY_BACKLIGHT_BEHAVIOUR_DEPRECATED)); } } // ------------------------------------------------------------------------------------ void shell_prefs_init(void) { s_backlight_intensity = prv_convert_backlight_percent_to_intensity(BOARD_CONFIG.backlight_on_percent); s_mutex = mutex_create(); SettingsFile file = {{0}}; if (settings_file_open(&file, SHELL_PREFS_FILE_NAME, SHELL_PREFS_FILE_LEN) != S_SUCCESS) { return; } prv_convert_deprecated_backlight_behaviour_key(&file); // Init state for each pref from our backing store uint32_t num_entries = ARRAY_LENGTH(s_prefs_table); const PrefsTableEntry *entry = s_prefs_table; for (uint32_t i = 0; i < num_entries; i++, entry++) { // Keys in the backing store include the null terminator, so we add 1 to key_len size_t key_len = strlen(entry->key) + 1; if (settings_file_get_len(&file, entry->key, key_len) == entry->value_len) { settings_file_get(&file, entry->key, key_len, entry->value, entry->value_len); } } settings_file_close(&file); } // ------------------------------------------------------------------------------------ // Find the PrefsTableEntry for the given key static const PrefsTableEntry *prv_prefs_entry(const uint8_t *key, size_t key_len) { uint32_t num_entries = ARRAY_LENGTH(s_prefs_table); const PrefsTableEntry *entry = s_prefs_table; for (uint32_t i = 0; i < num_entries; i++, entry++) { if (!strncmp((const char *)key, entry->key, key_len)) { return entry; } } PBL_LOG(LOG_LEVEL_WARNING, "Unrecognized key: %s", (const char *)key); return NULL; } // ------------------------------------------------------------------------------------ // Set the backing store for a pref static bool prv_set_pref_backing(const PrefsTableEntry *entry, const void *value, int value_len) { if (value_len != entry->value_len) { PBL_LOG(LOG_LEVEL_WARNING, "Attempt to set %s using invalid value_len of %"PRIu32"", entry->key, (uint32_t)value_len); return false; } status_t rv = E_ERROR; mutex_lock(s_mutex); { SettingsFile file = {{0}}; if (settings_file_open(&file, SHELL_PREFS_FILE_NAME, SHELL_PREFS_FILE_LEN) == S_SUCCESS) { // Keys in the backing store include the null terminator, so we add 1 to key_len rv = settings_file_set(&file, entry->key, strlen(entry->key) + 1, value, value_len); if (rv != S_SUCCESS) { PBL_LOG(LOG_LEVEL_WARNING, "Failed to set pref '%s' (%"PRIi32")", entry->key, (int32_t)rv); } settings_file_close(&file); } } mutex_unlock(s_mutex); return (rv == S_SUCCESS); } // ------------------------------------------------------------------------------------ // Convenience function used to update the state AND set the backing for a pref. This is // used by the functions below that are called by the firmware to change prefs (i.e. // shell_prefs_set.*, backlight_set.*, etc.). static void prv_pref_set(const char* key, const void *value, size_t val_len) { // Find the entry for this key const PrefsTableEntry *entry = prv_prefs_entry((const uint8_t *)key, strlen(key)); // validate the key and value length PBL_ASSERT(entry != NULL, "Key %s not found", key); PBL_ASSERT(val_len == entry->value_len, "Attempt to set %s using invalid value_len of %"PRIu32"", entry->key, (uint32_t)val_len); // Call the update handler bool success = entry->handler(value, val_len); PBL_ASSERT(success, "Failure to store new value for %s in settings file", key); // Update the backing store if (success) { prv_set_pref_backing(entry, value, val_len); } } // ------------------------------------------------------------------------------------ // Exported function used by blob_db API to set the backing store for a specific key bool prefs_private_write_backing(const uint8_t *key, size_t key_len, const void *value, int value_len) { const PrefsTableEntry *entry = prv_prefs_entry(key, key_len); if (!entry) { return false; } return prv_set_pref_backing(entry, value, value_len); } // ------------------------------------------------------------------------------------ // Exported function used by blob_db API to get the length of a value in our backing store int prefs_private_get_backing_len(const uint8_t *key, size_t key_len) { const PrefsTableEntry *entry = prv_prefs_entry(key, key_len); if (!entry) { return 0; } return entry->value_len; } // ------------------------------------------------------------------------------------ // Exported function used by blob_db API to read our backing store bool prefs_private_read_backing(const uint8_t *key, size_t key_len, void *value, int value_len) { const PrefsTableEntry *entry = prv_prefs_entry(key, key_len); if (!entry) { return false; } if (value_len != entry->value_len) { PBL_LOG(LOG_LEVEL_WARNING, "Attempt to read %s using invalid value_len of %"PRIu32"", entry->key, (uint32_t)value_len); return false; } bool success = false; mutex_lock(s_mutex); { SettingsFile file = {{0}}; if (settings_file_open(&file, SHELL_PREFS_FILE_NAME, SHELL_PREFS_FILE_LEN) == S_SUCCESS) { // Keys in the backing store include the null terminator, so we add 1 to key_len success = (settings_file_get(&file, entry->key, key_len + 1, value, value_len) == S_SUCCESS); settings_file_close(&file); } } mutex_unlock(s_mutex); return success; } // ------------------------------------------------------------------------------------ // Called from KernelMain when we get a blob DB event. We take this opportunity to update the state // of the given pref void prefs_private_handle_blob_db_event(PebbleBlobDBEvent *event) { if (event->type != BlobDBEventTypeInsert) { return; } const PrefsTableEntry *entry = prv_prefs_entry(event->key, event->key_len); if (!entry) { return; } // Read in the updated value from the backing store bool success = prefs_private_read_backing(event->key, event->key_len, entry->value, entry->value_len); if (success) { // Call the state update handler in case this pref needs to take other action besides // just updating the global if (!entry->handler(entry->value, entry->value_len)) { // If the handler returns false, that means it reset the global back to the default, // so we should write the new value to the backing prefs_private_write_backing(event->key, event->key_len, entry->value, entry->value_len); } } } // ======================================================================================== // Exported functions used by the firmware to read/change a preference. // IMPORTANT: When implementing the *set* call, be sure to call prv_pref_set(). This does // two things: // 1.) It validates that the stored global matches the type of the passed in argument // 2.) It insures that the flow will also work correctly for setting a pref from the // mobile side using a blob_db insert operation. bool shell_prefs_get_clock_24h_style(void) { return s_clock_24h; } UnitsDistance shell_prefs_get_units_distance(void) { return s_units_distance; } void shell_prefs_set_units_distance(UnitsDistance new_unit) { uint8_t uint_new_unit = new_unit; prv_pref_set(PREF_KEY_UNITS_DISTANCE, &uint_new_unit, sizeof(uint_new_unit)); } void shell_prefs_set_clock_24h_style(bool is24h) { prv_pref_set(PREF_KEY_CLOCK_24H, &is24h, sizeof(is24h)); } bool shell_prefs_is_timezone_source_manual(void) { return s_clock_timezone_source_is_manual; } void shell_prefs_set_timezone_source_manual(bool manual) { prv_pref_set(PREF_KEY_CLOCK_TIMEZONE_SOURCE_IS_MANUAL, &manual, sizeof(manual)); } void shell_prefs_set_automatic_timezone_id(int16_t timezone_id) { prv_pref_set(PREF_KEY_CLOCK_PHONE_TIMEZONE_ID, &timezone_id, sizeof(timezone_id)); } int16_t shell_prefs_get_automatic_timezone_id(void) { return s_clock_phone_timezone_id; } // Emulate the old BacklightBehaviour type for analytics. // This is a deprecated method and should not be called by new code. BacklightBehaviour backlight_get_behaviour(void) { if (s_backlight_enabled) { if (s_backlight_ambient_sensor_enabled) { return BacklightBehaviour_Auto; } else { return BacklightBehaviour_On; } } else { return BacklightBehaviour_Off; } } bool backlight_is_enabled(void) { return s_backlight_enabled; } void backlight_set_enabled(bool enabled) { prv_pref_set(PREF_KEY_BACKLIGHT_ENABLED, &enabled, sizeof(enabled)); } bool backlight_is_ambient_sensor_enabled(void) { #if INFINITE_BACKLIGHT return false; #endif return s_backlight_ambient_sensor_enabled; } void backlight_set_ambient_sensor_enabled(bool enabled) { prv_pref_set(PREF_KEY_BACKLIGHT_AMBIENT_SENSOR_ENABLED, &enabled, sizeof(enabled)); } uint32_t backlight_get_timeout_ms(void) { #if INFINITE_BACKLIGHT return UINT32_MAX; #endif return s_backlight_timeout_ms; } void backlight_set_timeout_ms(uint32_t timeout_ms) { prv_pref_set(PREF_KEY_BACKLIGHT_TIMEOUT_MS, &timeout_ms, sizeof(timeout_ms)); } uint16_t backlight_get_intensity(void) { return s_backlight_intensity; } uint8_t backlight_get_intensity_percent(void) { return (backlight_get_intensity() * 100) / BACKLIGHT_BRIGHTNESS_MAX; } void backlight_set_intensity_percent(uint8_t percent_intensity) { PBL_ASSERTN(percent_intensity > 0 && percent_intensity <= 100); uint16_t intensity = prv_convert_backlight_percent_to_intensity(percent_intensity); PBL_ASSERTN(intensity > BACKLIGHT_BRIGHTNESS_OFF); prv_pref_set(PREF_KEY_BACKLIGHT_INTENSITY, &intensity, sizeof(intensity)); } bool backlight_is_motion_enabled(void) { return s_backlight_motion_enabled; } void backlight_set_motion_enabled(bool enable) { prv_pref_set(PREF_KEY_BACKLIGHT_MOTION, &enable, sizeof(enable)); } bool shell_prefs_get_stationary_enabled(void) { return s_stationary_mode_enabled; } void shell_prefs_set_stationary_enabled(bool enabled) { prv_pref_set(PREF_KEY_STATIONARY, &enabled, sizeof(enabled)); } AppInstallId worker_preferences_get_default_worker(void) { return app_install_get_id_for_uuid(&s_default_worker); } void worker_preferences_set_default_worker(AppInstallId app_id) { Uuid uuid; app_install_get_uuid_for_install_id(app_id, &uuid); prv_pref_set(PREF_KEY_DEFAULT_WORKER, &uuid, sizeof(uuid)); } bool quick_launch_is_enabled(ButtonId button) { switch (button) { case BUTTON_ID_UP: return s_quick_launch_up.enabled; case BUTTON_ID_DOWN: return s_quick_launch_down.enabled; case BUTTON_ID_SELECT: return s_quick_launch_select.enabled; case BUTTON_ID_BACK: return s_quick_launch_back.enabled; case NUM_BUTTONS: break; } return false; } AppInstallId quick_launch_get_app(ButtonId button) { Uuid *uuid = NULL; switch (button) { case BUTTON_ID_UP: uuid = &s_quick_launch_up.uuid; break; case BUTTON_ID_DOWN: uuid = &s_quick_launch_down.uuid; break; case BUTTON_ID_SELECT: uuid = &s_quick_launch_select.uuid; break; case BUTTON_ID_BACK: uuid = &s_quick_launch_back.uuid; break; case NUM_BUTTONS: break; } PBL_ASSERTN(uuid); return app_install_get_id_for_uuid(uuid); } void quick_launch_set_app(ButtonId button, AppInstallId app_id) { QuickLaunchPreference pref = (QuickLaunchPreference) { .enabled = true, }; app_install_get_uuid_for_install_id(app_id, &pref.uuid); const char *key = NULL; switch (button) { case BUTTON_ID_UP: key = PREF_KEY_QUICK_LAUNCH_UP; break; case BUTTON_ID_DOWN: key = PREF_KEY_QUICK_LAUNCH_DOWN; break; case BUTTON_ID_SELECT: key = PREF_KEY_QUICK_LAUNCH_SELECT; break; case BUTTON_ID_BACK: key = PREF_KEY_QUICK_LAUNCH_BACK; break; case NUM_BUTTONS: break; } PBL_ASSERTN(key); prv_pref_set(key, &pref, sizeof(pref)); } void quick_launch_set_enabled(ButtonId button, bool enabled) { QuickLaunchPreference pref; const char *key = NULL; switch (button) { case BUTTON_ID_UP: pref = s_quick_launch_up; key = PREF_KEY_QUICK_LAUNCH_UP; break; case BUTTON_ID_DOWN: pref = s_quick_launch_down; key = PREF_KEY_QUICK_LAUNCH_DOWN; break; case BUTTON_ID_SELECT: pref = s_quick_launch_select; key = PREF_KEY_QUICK_LAUNCH_SELECT; break; case BUTTON_ID_BACK: pref = s_quick_launch_back; key = PREF_KEY_QUICK_LAUNCH_BACK; break; case NUM_BUTTONS: break; } PBL_ASSERTN(key); pref.enabled = enabled; prv_pref_set(key, &pref, sizeof(pref)); } void quick_launch_set_quick_launch_setup_opened(uint8_t version) { if (s_quick_launch_setup_opened != version) { s_quick_launch_setup_opened = version; prv_pref_set(PREF_KEY_QUICK_LAUNCH_SETUP_OPENED, &s_quick_launch_setup_opened, sizeof(s_quick_launch_setup_opened)); } } uint8_t quick_launch_get_quick_launch_setup_opened(void) { return s_quick_launch_setup_opened; } void watchface_set_default_install_id(AppInstallId app_id) { Uuid uuid; app_install_get_uuid_for_install_id(app_id, &uuid); prv_pref_set(PREF_KEY_DEFAULT_WATCHFACE, &uuid, sizeof(uuid)); } void welcome_set_welcome_version(uint8_t version) { if (s_welcome_version != version) { s_welcome_version = version; prv_pref_set(PREF_KEY_WELCOME_VERSION, &version, sizeof(version)); } } uint8_t welcome_get_welcome_version(void) { return s_welcome_version; } static bool prv_set_default_any_watchface_enumerate_callback(AppInstallEntry *entry, void *data) { if (!app_install_entry_is_watchface(entry) || app_install_entry_is_hidden(entry)) { return true; // continue search } watchface_set_default_install_id(entry->install_id); return false; } AppInstallId watchface_get_default_install_id(void) { AppInstallId app_id = app_install_get_id_for_uuid(&s_default_watchface); AppInstallEntry entry; if (app_id == INSTALL_ID_INVALID || !app_install_get_entry_for_install_id(app_id, &entry) || !app_install_entry_is_watchface(&entry)) { app_install_enumerate_entries( prv_set_default_any_watchface_enumerate_callback, NULL); app_id = app_install_get_id_for_uuid(&s_default_watchface); } return app_id; } void system_theme_set_content_size(PreferredContentSize content_size) { if (content_size >= NumPreferredContentSizes) { PBL_LOG(LOG_LEVEL_WARNING, "Ignoring attempt to set content size to invalid size %d", content_size); return; } const uint8_t content_size_uint = content_size; prv_pref_set(PREF_KEY_TEXT_STYLE, &content_size_uint, sizeof(content_size_uint)); } PreferredContentSize system_theme_get_content_size(void) { return system_theme_convert_host_content_size_to_runtime_platform( (PreferredContentSize)s_text_style); } bool shell_prefs_get_language_english(void) { return s_language_english; } void shell_prefs_set_language_english(bool english) { prv_pref_set(PREF_KEY_LANG_ENGLISH, &english, sizeof(english)); } void shell_prefs_toggle_language_english(void) { shell_prefs_set_language_english(!shell_prefs_get_language_english()); } #if CAPABILITY_HAS_HEALTH_TRACKING static void prv_activity_pref_set(void) { prv_pref_set(PREF_KEY_ACTIVITY_PREFERENCES, &s_activity_preferences, sizeof(s_activity_preferences)); } time_t activity_prefs_get_activation_time(void) { return s_activity_activation_timestamp; } void activity_prefs_set_activated(void) { if (s_activity_activation_timestamp == 0) { s_activity_activation_timestamp = rtc_get_time(); prv_pref_set(PREF_KEY_ACTIVITY_ACTIVATED_TIMESTAMP, &s_activity_activation_timestamp, sizeof(s_activity_activation_timestamp)); } } bool activity_prefs_has_activation_delay_insight_fired(ActivationDelayInsightType type) { return (s_activity_activation_delay_insight & (1 << type)); } void activity_prefs_set_activation_delay_insight_fired(ActivationDelayInsightType type) { s_activity_activation_delay_insight |= (1 << type); prv_pref_set(PREF_KEY_ACTIVITY_ACTIVATION_DELAY_INSIGHT, &s_activity_activation_delay_insight, sizeof(s_activity_activation_delay_insight)); } uint8_t activity_prefs_get_health_app_opened_version(void) { return s_activity_prefs_health_app_opened; } void activity_prefs_set_health_app_opened_version(uint8_t version) { if (s_activity_prefs_health_app_opened != version) { s_activity_prefs_health_app_opened = version; prv_pref_set(PREF_KEY_ACTIVITY_HEALTH_APP_OPENED, &s_activity_prefs_health_app_opened, sizeof(s_activity_prefs_health_app_opened)); } } uint8_t activity_prefs_get_workout_app_opened_version(void) { return s_activity_prefs_workout_app_opened; } void activity_prefs_set_workout_app_opened_version(uint8_t version) { if (s_activity_prefs_workout_app_opened != version) { s_activity_prefs_workout_app_opened = version; prv_pref_set(PREF_KEY_ACTIVITY_WORKOUT_APP_OPENED, &s_activity_prefs_workout_app_opened, sizeof(s_activity_prefs_workout_app_opened)); } } bool activity_prefs_activity_insights_are_enabled(void) { return s_activity_preferences.activity_insights_enabled; } void activity_prefs_activity_insights_set_enabled(bool enable) { s_activity_preferences.activity_insights_enabled = enable; prv_activity_pref_set(); } bool activity_prefs_sleep_insights_are_enabled(void) { return s_activity_preferences.sleep_insights_enabled; } void activity_prefs_sleep_insights_set_enabled(bool enable) { s_activity_preferences.sleep_insights_enabled = enable; prv_activity_pref_set(); } bool activity_prefs_tracking_is_enabled(void) { return s_activity_preferences.tracking_enabled; } void activity_prefs_tracking_set_enabled(bool enable) { s_activity_preferences.tracking_enabled = enable; prv_activity_pref_set(); } void activity_prefs_set_height_mm(uint16_t height_mm) { s_activity_preferences.height_mm = height_mm; prv_activity_pref_set(); } uint16_t activity_prefs_get_height_mm(void) { return s_activity_preferences.height_mm; } void activity_prefs_set_weight_dag(uint16_t weight_dag) { s_activity_preferences.weight_dag = weight_dag; prv_activity_pref_set(); } uint16_t activity_prefs_get_weight_dag(void) { return s_activity_preferences.weight_dag; } void activity_prefs_set_gender(ActivityGender gender) { s_activity_preferences.gender = gender; prv_activity_pref_set(); } ActivityGender activity_prefs_get_gender(void) { return s_activity_preferences.gender; } void activity_prefs_set_age_years(uint8_t age_years) { s_activity_preferences.age_years = age_years; prv_activity_pref_set(); } uint8_t activity_prefs_get_age_years(void) { return s_activity_preferences.age_years; } uint8_t activity_prefs_heart_get_resting_hr(void) { return s_activity_hr_preferences.resting_hr; } uint8_t activity_prefs_heart_get_elevated_hr(void) { return s_activity_hr_preferences.elevated_hr; } uint8_t activity_prefs_heart_get_max_hr(void) { return s_activity_hr_preferences.max_hr; } uint8_t activity_prefs_heart_get_zone1_threshold(void) { return s_activity_hr_preferences.zone1_threshold; } uint8_t activity_prefs_heart_get_zone2_threshold(void) { return s_activity_hr_preferences.zone2_threshold; } uint8_t activity_prefs_heart_get_zone3_threshold(void) { return s_activity_hr_preferences.zone3_threshold; } bool activity_prefs_heart_rate_is_enabled(void) { return s_activity_hrm_preferences.enabled; } void alarm_prefs_set_alarms_app_opened(uint8_t version) { if (s_alarms_app_opened != version) { s_alarms_app_opened = version; prv_pref_set(PREF_KEY_ALARMS_APP_OPENED, &s_alarms_app_opened, sizeof(s_alarms_app_opened)); } } uint8_t alarm_prefs_get_alarms_app_opened(void) { return s_alarms_app_opened; } #endif /* CAPABILITY_HAS_HEALTH_TRACKING */ #if PLATFORM_SPALDING void shell_prefs_set_display_offset(GPoint offset) { const GPoint user_offset = gpoint_sub(offset, mfg_info_get_disp_offsets()); prv_pref_set(PREF_KEY_DISPLAY_USER_OFFSET, &user_offset, sizeof(user_offset)); } GPoint shell_prefs_get_display_offset(void) { return gpoint_add(s_display_user_offset, mfg_info_get_disp_offsets()); } void shell_prefs_display_offset_init(void) { display_set_offset(shell_prefs_get_display_offset()); } bool shell_prefs_should_prompt_display_calibration(void) { return s_should_prompt_display_calibration; } void shell_prefs_set_should_prompt_display_calibration(bool should_prompt) { s_should_prompt_display_calibration = should_prompt; prv_pref_set(PREF_KEY_SHOULD_PROMPT_DISPLAY_CALIBRATION, &s_should_prompt_display_calibration, sizeof(s_should_prompt_display_calibration)); } #endif #if CAPABILITY_HAS_TIMELINE_PEEK void timeline_prefs_set_settings_opened(uint8_t version) { prv_pref_set(PREF_KEY_TIMELINE_SETTINGS_OPENED, &version, sizeof(version)); } uint8_t timeline_prefs_get_settings_opened(void) { return s_timeline_settings_opened; } void timeline_peek_prefs_set_enabled(bool enabled) { prv_pref_set(PREF_KEY_TIMELINE_PEEK_ENABLED, &enabled, sizeof(enabled)); } bool timeline_peek_prefs_get_enabled(void) { return s_timeline_peek_enabled; } void timeline_peek_prefs_set_before_time(uint16_t before_time_m) { prv_pref_set(PREF_KEY_TIMELINE_PEEK_BEFORE_TIME_M, &before_time_m, sizeof(before_time_m)); } uint16_t timeline_peek_prefs_get_before_time(void) { return s_timeline_peek_before_time_m; } #else uint16_t timeline_peek_prefs_get_before_time(void) { return TIMELINE_PEEK_DEFAULT_SHOW_BEFORE_TIME_S; } #endif