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

264 lines
8.2 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 <inttypes.h>
#include <string.h>
#include "activity.h"
#include "insights_settings.h"
#include "os/mutex.h"
#include "services/normal/filesystem/pfs.h"
#include "services/normal/settings/settings_file.h"
#include "system/logging.h"
#include "util/size.h"
#define ACTIVITY_INSIGHTS_SETTINGS_FILENAME "insights"
#define ACTIVITY_INSIGHTS_SETTINGS_DEFAULT_FILE_SIZE 4096
#define ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY "version"
#define ACTIVITY_INSIGHTS_SETTINGS_DEFAULT_VERSION 0
#define ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION 4
static PebbleMutex *s_insight_settings_mutex;
#define ACTIVITY_INSIGHTS_SETTINGS_SLEEP_REWARD_DEFAULT { \
.version = ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION, \
.enabled = false, \
.reward = { \
.min_days_data = 6, \
.continuous_min_days_data = 2, \
.target_qualifying_days = 2, \
.target_percent_of_median = 120, \
.notif_min_interval_seconds = 7 * SECONDS_PER_DAY, \
.sleep.trigger_after_wakeup_seconds = 2 * SECONDS_PER_HOUR \
} \
}
#define ACTIVITY_INSIGHTS_SETTINGS_SLEEP_SUMMARY_DEFAULT { \
.version = ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION, \
.enabled = true, \
.summary = { \
.above_avg_threshold = 10, \
.below_avg_threshold = -10, \
.fail_threshold = -50, \
.sleep = { \
.max_fail_minutes = 7 * MINUTES_PER_HOUR, \
.trigger_notif_seconds = 30 * SECONDS_PER_MINUTE, \
.trigger_notif_activity = 20, \
.trigger_notif_active_minutes = 5 \
} \
} \
}
#define ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_REWARD_DEFAULT { \
.version = ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION, \
.enabled = false, \
.reward = {\
.min_days_data = 6, \
.continuous_min_days_data = 0, \
.target_qualifying_days = 0, \
.target_percent_of_median = 150, \
.notif_min_interval_seconds = 1 * SECONDS_PER_DAY, \
.activity = { \
.trigger_active_minutes = 2, \
.trigger_steps_per_minute = 50 \
} \
} \
}
#define ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_SUMMARY_DEFAULT { \
.version = ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION, \
.enabled = true, \
.summary = { \
.above_avg_threshold = 10, \
.below_avg_threshold = -10, \
.fail_threshold = -50, \
.activity = { \
.trigger_minute = (20 * MINUTES_PER_HOUR) + 30, \
.update_threshold_steps = 1000, \
.update_max_interval_seconds = 30 * SECONDS_PER_MINUTE, \
.show_notification = true, \
.max_fail_steps = 10000, \
} \
} \
}
#define ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_SESSION_DEFAULT { \
.version = ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION, \
.enabled = true, \
.session = { \
.show_notification = true, \
.activity = { \
.trigger_elapsed_minutes = 20, \
.trigger_cooldown_minutes = 10, \
}, \
} \
}
typedef struct {
const char *key;
ActivityInsightSettings default_val;
} AISDefault;
static const AISDefault AIS_DEFAULTS[] = {
{
.key = ACTIVITY_INSIGHTS_SETTINGS_SLEEP_REWARD,
.default_val = ACTIVITY_INSIGHTS_SETTINGS_SLEEP_REWARD_DEFAULT
},
{
.key = ACTIVITY_INSIGHTS_SETTINGS_SLEEP_SUMMARY,
.default_val = ACTIVITY_INSIGHTS_SETTINGS_SLEEP_SUMMARY_DEFAULT
},
{
.key = ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_REWARD,
.default_val = ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_REWARD_DEFAULT
},
{
.key = ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_SUMMARY,
.default_val = ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_SUMMARY_DEFAULT
},
{
.key = ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_SESSION,
.default_val = ACTIVITY_INSIGHTS_SETTINGS_ACTIVITY_SESSION_DEFAULT
},
};
// Return true if we successfully opened the file
static bool prv_open_settings_and_lock(SettingsFile *file) {
mutex_lock(s_insight_settings_mutex);
if (settings_file_open(file, ACTIVITY_INSIGHTS_SETTINGS_FILENAME,
ACTIVITY_INSIGHTS_SETTINGS_DEFAULT_FILE_SIZE) == S_SUCCESS) {
return true;
} else {
mutex_unlock(s_insight_settings_mutex);
return false;
}
}
// Close the settings file and release the lock
static void prv_close_settings_and_unlock(SettingsFile *file) {
settings_file_close(file);
mutex_unlock(s_insight_settings_mutex);
}
void activity_insights_settings_init(void) {
// Create our mutex
s_insight_settings_mutex = mutex_create();
SettingsFile file;
if (settings_file_open(&file,
ACTIVITY_INSIGHTS_SETTINGS_FILENAME,
ACTIVITY_INSIGHTS_SETTINGS_DEFAULT_FILE_SIZE) == S_SUCCESS) {
if (!settings_file_exists(&file,
ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY,
strlen(ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY))) {
// init version to 0
const uint16_t default_version = ACTIVITY_INSIGHTS_SETTINGS_DEFAULT_VERSION;
settings_file_set(&file,
ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY,
strlen(ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY),
&default_version,
sizeof(uint16_t));
}
settings_file_close(&file);
return;
}
PBL_LOG(LOG_LEVEL_ERROR, "Failed to create activity insights settings file");
}
uint16_t activity_insights_settings_get_version(void) {
uint16_t version = ACTIVITY_INSIGHTS_SETTINGS_DEFAULT_VERSION;
SettingsFile file;
if (prv_open_settings_and_lock(&file)) {
settings_file_get(&file,
ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY,
strlen(ACTIVITY_INSIGHTS_SETTINGS_VERSION_KEY),
&version,
sizeof(uint16_t));
prv_close_settings_and_unlock(&file);
}
return version;
}
bool activity_insights_settings_read(const char *insight_name,
ActivityInsightSettings *settings_out) {
bool rv = false;
*settings_out = (ActivityInsightSettings) {};
SettingsFile file;
if (prv_open_settings_and_lock(&file)) {
if (settings_file_get(&file,
insight_name, strlen(insight_name),
settings_out, sizeof(*settings_out)) != S_SUCCESS) {
PBL_LOG(LOG_LEVEL_DEBUG, "Didn't find insight with key %s", insight_name);
goto close;
}
if (settings_out->version != ACTIVITY_INSIGHTS_SETTINGS_CURRENT_STRUCT_VERSION) {
// versions don't match, bail out!
PBL_LOG(LOG_LEVEL_WARNING, "activity insights struct version mismatch");
goto close;
}
rv = true;
close:
prv_close_settings_and_unlock(&file);
}
if (!rv) {
// Use default value if we didn't find anything else
for (unsigned i = 0; i < ARRAY_LENGTH(AIS_DEFAULTS); ++i) {
if (strcmp(insight_name, AIS_DEFAULTS[i].key) == 0) {
PBL_LOG(LOG_LEVEL_DEBUG, "Using default for insight %s", insight_name);
*settings_out = AIS_DEFAULTS[i].default_val;
rv = true;
}
}
}
return rv;
}
bool activity_insights_settings_write(const char *insight_name,
ActivityInsightSettings *settings) {
bool rv = false;
SettingsFile file;
if (prv_open_settings_and_lock(&file)) {
if (settings_file_set(&file,
insight_name, strlen(insight_name),
settings, sizeof(*settings)) != S_SUCCESS) {
PBL_LOG(LOG_LEVEL_WARNING, "Unable to save insight setting with key %s", insight_name);
} else {
rv = true;
}
prv_close_settings_and_unlock(&file);
}
return rv;
}
PFSCallbackHandle activity_insights_settings_watch(PFSFileChangedCallback callback) {
return pfs_watch_file(ACTIVITY_INSIGHTS_SETTINGS_FILENAME, callback, FILE_CHANGED_EVENT_CLOSED,
NULL);
}
void activity_insights_settings_unwatch(PFSCallbackHandle cb_handle) {
pfs_unwatch_file(cb_handle);
}