mirror of
https://github.com/google/pebble.git
synced 2025-11-23 07:50:55 -05:00
Import of the watch repository from Pebble
This commit is contained in:
92
src/fw/shell/normal/app_idle_timeout.c
Normal file
92
src/fw/shell/normal/app_idle_timeout.c
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 "app_idle_timeout.h"
|
||||
|
||||
#include "kernel/event_loop.h"
|
||||
#include "os/tick.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "shell/shell.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
|
||||
static const int WATCHFACE_TIMEOUT_MS = 30000;
|
||||
|
||||
TimerID s_timer;
|
||||
bool s_app_paused = false;
|
||||
bool s_app_started = false;
|
||||
|
||||
#ifndef NO_WATCH_TIMEOUT
|
||||
static void prv_kernel_callback_watchface_launch(void* data) {
|
||||
watchface_launch_default(shell_get_watchface_compositor_animation(true /* watchface_is_dest */));
|
||||
}
|
||||
|
||||
static void prv_timeout_expired(void *cb_data) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "App idle timeout hit! launching watchface");
|
||||
launcher_task_add_callback(prv_kernel_callback_watchface_launch, NULL);
|
||||
}
|
||||
|
||||
static void prv_start_timer(bool create) {
|
||||
if (create) {
|
||||
s_timer = new_timer_create();
|
||||
}
|
||||
|
||||
if (s_timer != TIMER_INVALID_ID && !s_app_paused && s_app_started) {
|
||||
bool success = new_timer_start(s_timer, WATCHFACE_TIMEOUT_MS, prv_timeout_expired,
|
||||
NULL, 0 /* flags */);
|
||||
PBL_ASSERTN(success);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_idle_timeout_start(void) {
|
||||
PBL_ASSERTN(s_timer == TIMER_INVALID_ID);
|
||||
|
||||
s_app_started = true;
|
||||
#ifndef NO_WATCH_TIMEOUT
|
||||
prv_start_timer(true /* create a timer */);
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_idle_timeout_stop(void) {
|
||||
if (s_timer != TIMER_INVALID_ID) {
|
||||
new_timer_delete(s_timer);
|
||||
s_timer = TIMER_INVALID_ID;
|
||||
s_app_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
void app_idle_timeout_pause(void) {
|
||||
if (s_timer != TIMER_INVALID_ID) {
|
||||
new_timer_stop(s_timer);
|
||||
}
|
||||
s_app_paused = true;
|
||||
}
|
||||
|
||||
void app_idle_timeout_resume(void) {
|
||||
s_app_paused = false;
|
||||
#ifndef NO_WATCH_TIMEOUT
|
||||
prv_start_timer(false /* do not create a timer */);
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_idle_timeout_refresh(void) {
|
||||
#ifndef NO_WATCH_TIMEOUT
|
||||
prv_start_timer(false /* do not create a timer */);
|
||||
#endif
|
||||
}
|
||||
36
src/fw/shell/normal/app_idle_timeout.h
Normal file
36
src/fw/shell/normal/app_idle_timeout.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! Start using the idle timeout for the current app.
|
||||
void app_idle_timeout_start(void);
|
||||
|
||||
//! Stop using the idle timeout for the current app. This is safe to call even if the idle timeout wasn't running.
|
||||
void app_idle_timeout_stop(void);
|
||||
|
||||
//! Pause the idle timeout for the current app. This is safe to call even if the idle timeout wasn't running
|
||||
//! previously.
|
||||
void app_idle_timeout_pause(void);
|
||||
|
||||
//! Resume the idle timeout for the current app. This is safe to call even if the idle timeout wasn't running
|
||||
//! previously.
|
||||
void app_idle_timeout_resume(void);
|
||||
|
||||
//! Reset the timeout. Call this whenever there is activity that should prevent the idle timeout from firing. This
|
||||
//! is safe to call even if the idle timeout wasn't running previously.
|
||||
void app_idle_timeout_refresh(void);
|
||||
|
||||
188
src/fw/shell/normal/battery_ui.c
Normal file
188
src/fw/shell/normal/battery_ui.c
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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 "battery_ui.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "applib/ui/dialogs/dialog_private.h"
|
||||
#include "applib/ui/dialogs/simple_dialog.h"
|
||||
#include "applib/ui/window_stack.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/ui/kernel_ui.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/battery/battery_curve.h"
|
||||
#include "services/common/clock.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/common/light.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/time/time.h"
|
||||
|
||||
typedef void (*DialogUpdateFn)(Dialog *, void *);
|
||||
|
||||
static Dialog *s_dialog = NULL;
|
||||
|
||||
typedef struct {
|
||||
uint32_t percent;
|
||||
GColor background_color;
|
||||
ResourceId warning_icon;
|
||||
} BatteryWarningDisplayData;
|
||||
|
||||
// UI Callbacks
|
||||
///////////////////////
|
||||
|
||||
static const GColor s_warning_color[] = {
|
||||
{ .argb = GColorLightGrayARGB8 },
|
||||
{ .argb = GColorRedARGB8 },
|
||||
};
|
||||
|
||||
static const ResourceId s_warning_icon[] = {
|
||||
RESOURCE_ID_BATTERY_ICON_LOW_LARGE,
|
||||
RESOURCE_ID_BATTERY_ICON_VERY_LOW_LARGE
|
||||
};
|
||||
|
||||
static void prv_update_ui_fully_charged(Dialog *dialog, void *ignored) {
|
||||
dialog_set_text(dialog, i18n_get("Fully Charged", dialog));
|
||||
dialog_set_background_color(dialog, GColorKellyGreen);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_FULL_LARGE);
|
||||
}
|
||||
|
||||
static void prv_update_ui_charging(Dialog *dialog, void *ignored) {
|
||||
dialog_set_text(dialog, i18n_get("Charging", dialog));
|
||||
dialog_set_background_color(dialog, GColorLightGray);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_CHARGING_LARGE);
|
||||
}
|
||||
|
||||
static void prv_update_ui_warning(Dialog *dialog, void *context) {
|
||||
const BatteryWarningDisplayData *data = context;
|
||||
const uint32_t percent = data->percent;
|
||||
dialog_set_background_color(dialog, data->background_color);
|
||||
const size_t warning_length = 64;
|
||||
char buffer[warning_length];
|
||||
const uint32_t battery_hours_left = battery_curve_get_hours_remaining(percent);
|
||||
const char *message = clock_get_relative_daypart_string(rtc_get_time(), battery_hours_left);
|
||||
|
||||
if (message) {
|
||||
snprintf(buffer, warning_length, i18n_get("Powered 'til %s", dialog),
|
||||
i18n_get(message, dialog));
|
||||
dialog_set_text(dialog, buffer);
|
||||
}
|
||||
|
||||
dialog_set_icon(dialog, data->warning_icon);
|
||||
}
|
||||
|
||||
static void prv_dialog_on_unload(void *context) {
|
||||
Dialog *dialog = context;
|
||||
i18n_free_all(dialog);
|
||||
if (dialog == s_dialog) {
|
||||
s_dialog = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_display_modal(WindowStack *stack, DialogUpdateFn update_fn, void *data) {
|
||||
if (s_dialog) {
|
||||
update_fn(s_dialog, data);
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleDialog *new_simple_dialog = simple_dialog_create(
|
||||
WINDOW_NAME("Battery Status"));
|
||||
|
||||
Dialog *new_dialog = simple_dialog_get_dialog(new_simple_dialog);
|
||||
dialog_set_callbacks(new_dialog, &(DialogCallbacks) {
|
||||
.unload = prv_dialog_on_unload,
|
||||
}, NULL);
|
||||
update_fn(new_dialog, data);
|
||||
|
||||
Dialog *old_dialog = s_dialog;
|
||||
s_dialog = new_dialog;
|
||||
simple_dialog_push(new_simple_dialog, stack);
|
||||
|
||||
#if PBL_ROUND
|
||||
// For circular display, to fit some battery_ui messages requires 3 lines
|
||||
// Simple dialog only allows up to 2 lines, so adjust here
|
||||
// This has to occur after the dialog push has been called
|
||||
TextLayer *text_layer = &new_simple_dialog->dialog.text_layer;
|
||||
GContext *ctx = graphics_context_get_current_context();
|
||||
const int font_height = fonts_get_font_height(text_layer->font);
|
||||
const int text_cap_height = fonts_get_font_cap_offset(text_layer->font);
|
||||
const int max_text_height = 2 * font_height + text_cap_height;
|
||||
const int32_t text_height = text_layer_get_content_size(ctx, text_layer).h;
|
||||
if (text_height > max_text_height) {
|
||||
// Values used below were to improve visual aesthetics and were reviewed by design
|
||||
const int num_lines = 3;
|
||||
const int line_spacing_delta = -4;
|
||||
const int text_shift_y = -2;
|
||||
const int text_box_height = (font_height + text_cap_height) * num_lines +
|
||||
line_spacing_delta * (num_lines - 1);
|
||||
const int text_flow_inset = 6; // Modify to allow longer central lines
|
||||
text_layer_enable_screen_text_flow_and_paging(text_layer, text_flow_inset);
|
||||
text_layer_set_size(text_layer, GSize(DISP_COLS, text_box_height));
|
||||
text_layer->layer.frame.origin.y += text_shift_y;
|
||||
text_layer_set_line_spacing_delta(text_layer, line_spacing_delta);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (old_dialog) {
|
||||
dialog_pop(old_dialog);
|
||||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
////////////////////
|
||||
|
||||
void battery_ui_display_plugged(void) {
|
||||
// If we're plugged in for charging, we want to alert the user of this,
|
||||
// but we don't want to overlay ourselves over anything they may have
|
||||
// on the screen at the moment.
|
||||
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityGeneric);
|
||||
prv_display_modal(stack, prv_update_ui_charging, NULL);
|
||||
}
|
||||
|
||||
void battery_ui_display_fully_charged(void) {
|
||||
// If we're plugged in (charged), we want to alert the user of this,
|
||||
// but we don't want to overlay ourselves over anything they may have
|
||||
// on the screen at the moment.
|
||||
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityGeneric);
|
||||
prv_display_modal(stack, prv_update_ui_fully_charged, NULL);
|
||||
}
|
||||
|
||||
void battery_ui_display_warning(uint32_t percent, BatteryUIWarningLevel warning_level) {
|
||||
// If we're not plugged in, that means we hit a critical power notification,
|
||||
// so we want to alert the user, subverting any non-critical windows they
|
||||
// have on the screen.
|
||||
WindowStack *stack = modal_manager_get_window_stack(ModalPriorityAlert);
|
||||
|
||||
BatteryWarningDisplayData display_data = {
|
||||
.percent = percent,
|
||||
.background_color = s_warning_color[warning_level],
|
||||
.warning_icon = s_warning_icon[warning_level],
|
||||
};
|
||||
prv_display_modal(stack, prv_update_ui_warning, &display_data);
|
||||
}
|
||||
|
||||
void battery_ui_dismiss_modal(void) {
|
||||
if (s_dialog) {
|
||||
dialog_pop(s_dialog);
|
||||
s_dialog = NULL;
|
||||
}
|
||||
}
|
||||
48
src/fw/shell/normal/battery_ui.h
Normal file
48
src/fw/shell/normal/battery_ui.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "services/common/battery/battery_monitor.h"
|
||||
|
||||
typedef enum BatteryUIWarningLevel {
|
||||
BatteryUIWarningLevel_None = -1,
|
||||
BatteryUIWarningLevel_Low,
|
||||
BatteryUIWarningLevel_VeryLow
|
||||
} BatteryUIWarningLevel;
|
||||
|
||||
//! Process the incoming battery state change notification
|
||||
void battery_ui_handle_state_change_event(PreciseBatteryChargeState new_state);
|
||||
|
||||
//! Handle shutting down the watch.
|
||||
//!
|
||||
//! If the watch is plugged in at the time, a "shut down while charging" UI is
|
||||
//! displayed to give the user feedback on the charge state. Standby will be
|
||||
//! entered once the watch is unplugged.
|
||||
void battery_ui_handle_shut_down(void);
|
||||
|
||||
//! Show the 'battery charging' modal dialog
|
||||
void battery_ui_display_plugged(void);
|
||||
|
||||
//! Show the 'battery charged' modal dialog
|
||||
void battery_ui_display_fully_charged(void);
|
||||
|
||||
//! Show the 'battery critical' modal dialog
|
||||
void battery_ui_display_warning(uint32_t percent, BatteryUIWarningLevel warning_level);
|
||||
|
||||
//! Dismiss the battery UI modal window.
|
||||
void battery_ui_dismiss_modal(void);
|
||||
292
src/fw/shell/normal/battery_ui_fsm.c
Normal file
292
src/fw/shell/normal/battery_ui_fsm.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/*
|
||||
* 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 "battery_ui.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "applib/ui/vibes.h"
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "apps/system_apps/battery_critical_app.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "kernel/util/standby.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "services/common/battery/battery_curve.h"
|
||||
#include "services/common/status_led.h"
|
||||
#include "services/common/vibe_pattern.h"
|
||||
#include "services/normal/notifications/do_not_disturb.h"
|
||||
#include "services/normal/vibes/vibe_intensity.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "util/ratio.h"
|
||||
#include "util/size.h"
|
||||
|
||||
// The Battery UI state machine keeps track of when to notify the user of a
|
||||
// change in battery charge state, and when to automatically dismiss the status
|
||||
// modal window.
|
||||
|
||||
#define MAX_TRANSITIONS 6
|
||||
|
||||
typedef void (*EntryFunc)(void *);
|
||||
typedef void (*ExitFunc)(void);
|
||||
|
||||
typedef enum BatteryUIStateID {
|
||||
BatteryInvalid,
|
||||
BatteryGood,
|
||||
BatteryWarning,
|
||||
BatteryLowPower,
|
||||
BatteryCritical,
|
||||
BatteryCharging,
|
||||
BatteryFullyCharged, // plugged but not charging (aka 100%)
|
||||
BatteryShutdownCharging
|
||||
} BatteryUIStateID;
|
||||
|
||||
typedef struct BatteryUIState {
|
||||
EntryFunc enter;
|
||||
ExitFunc exit;
|
||||
BatteryUIStateID next_state[MAX_TRANSITIONS];
|
||||
} BatteryUIState;
|
||||
|
||||
static void prv_display_warning(void *data);
|
||||
static void prv_dismiss_warning(void);
|
||||
static void prv_enter_low_power(void *ignored);
|
||||
static void prv_exit_low_power(void);
|
||||
static void prv_enter_critical(void *ignored);
|
||||
static void prv_exit_critical(void);
|
||||
static void prv_display_plugged(void *data);
|
||||
static void prv_dismiss_plugged(void);
|
||||
static void prv_display_fully_charged(void *data);
|
||||
static void prv_dismiss_fully_charged(void);
|
||||
// TODO PBL-39883: Replace w/ QUIRK_RESET_ON_SHUTDOWN_WHILE_CHARGING once arbitrary prefixes land
|
||||
#if PLATFORM_TINTIN || PLATFORM_SILK
|
||||
static void prv_shutdown(void *ignored);
|
||||
#else
|
||||
static void prv_enter_shutdown_charging(void *ignored);
|
||||
#endif
|
||||
|
||||
static const BatteryUIState ui_states[] = {
|
||||
[BatteryGood] = { .next_state = {
|
||||
BatteryWarning, BatteryLowPower, BatteryCritical, BatteryCharging, BatteryFullyCharged
|
||||
}},
|
||||
[BatteryWarning] = { .enter = prv_display_warning, .exit = prv_dismiss_warning, .next_state = {
|
||||
BatteryGood, BatteryWarning, BatteryLowPower, BatteryCharging
|
||||
}},
|
||||
[BatteryLowPower] = { .enter = prv_enter_low_power, .exit = prv_exit_low_power, .next_state = {
|
||||
BatteryWarning, BatteryCritical, BatteryCharging
|
||||
}},
|
||||
[BatteryCritical] = { .enter = prv_enter_critical, .exit = prv_exit_critical, .next_state = {
|
||||
BatteryLowPower, BatteryCharging
|
||||
}},
|
||||
[BatteryCharging] = { .enter = prv_display_plugged, .exit = prv_dismiss_plugged, .next_state = {
|
||||
BatteryGood, BatteryWarning, BatteryLowPower,
|
||||
BatteryCritical, BatteryFullyCharged, BatteryShutdownCharging
|
||||
}},
|
||||
[BatteryFullyCharged] = { .enter = prv_display_fully_charged, .exit = prv_dismiss_fully_charged,
|
||||
.next_state = {
|
||||
BatteryGood, BatteryWarning, BatteryLowPower, BatteryCritical, BatteryShutdownCharging
|
||||
}},
|
||||
// TODO PBL-39883: Replace w/ QUIRK_RESET_ON_SHUTDOWN_WHILE_CHARGING once arbitrary prefixes land
|
||||
#if PLATFORM_TINTIN || PLATFORM_SILK
|
||||
[BatteryShutdownCharging] = { .enter = prv_shutdown }
|
||||
#else
|
||||
[BatteryShutdownCharging] = { .enter = prv_enter_shutdown_charging }
|
||||
#endif
|
||||
};
|
||||
|
||||
static BatteryUIStateID s_state = BatteryGood;
|
||||
static BatteryUIWarningLevel s_warning_points_index = -1;
|
||||
|
||||
#if PLATFORM_SPALDING
|
||||
/* first warning for S4 is at 12 hours remaining, second at 6 hours remaining */
|
||||
static const uint8_t s_warning_points[] = { 12, 6 };
|
||||
#else
|
||||
/* first warning is at 18 hours remaining, second at 12 hours remaining */
|
||||
static const uint8_t s_warning_points[] = { 18, 12 };
|
||||
#endif
|
||||
|
||||
// State functions
|
||||
|
||||
static void prv_display_warning(void *data) {
|
||||
const uint8_t percent = ratio32_to_percent(((PreciseBatteryChargeState *)data)->charge_percent);
|
||||
bool new_warning = false;
|
||||
const BatteryUIWarningLevel num_points = ARRAY_LENGTH(s_warning_points) - 1;
|
||||
|
||||
while (s_warning_points_index < num_points && (percent <=
|
||||
battery_curve_get_percent_remaining(s_warning_points[s_warning_points_index + 1]))) {
|
||||
s_warning_points_index++;
|
||||
new_warning = true;
|
||||
}
|
||||
|
||||
if (new_warning) {
|
||||
if (!do_not_disturb_is_active()) {
|
||||
vibes_short_pulse();
|
||||
}
|
||||
battery_ui_display_warning(percent, s_warning_points_index);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_dismiss_warning(void) {
|
||||
battery_ui_dismiss_modal();
|
||||
s_warning_points_index = -1;
|
||||
}
|
||||
|
||||
static void prv_enter_low_power(void *ignored) {
|
||||
#ifndef RECOVERY_FW
|
||||
watchface_start_low_power();
|
||||
modal_manager_pop_all_below_priority(ModalPriorityAlarm);
|
||||
modal_manager_set_min_priority(ModalPriorityAlarm);
|
||||
// Override the vibe intensity to Medium in low-power mode
|
||||
vibes_set_default_vibe_strength(get_strength_for_intensity(VibeIntensityMedium));
|
||||
#else
|
||||
app_manager_launch_new_app(&(AppLaunchConfig) {
|
||||
.md = prf_low_power_app_get_info(),
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_exit_low_power(void) {
|
||||
#ifndef RECOVERY_FW
|
||||
modal_manager_set_min_priority(ModalPriorityMin);
|
||||
watchface_launch_default(NULL);
|
||||
vibe_intensity_set(vibe_intensity_get());
|
||||
#else
|
||||
app_manager_close_current_app(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void prv_enter_critical(void *ignored) {
|
||||
if (!do_not_disturb_is_active()) {
|
||||
vibes_short_pulse();
|
||||
}
|
||||
// in case there is a warning on screen
|
||||
modal_manager_pop_all();
|
||||
modal_manager_set_min_priority(ModalPriorityMax);
|
||||
app_manager_put_launch_app_event(&(AppLaunchEventConfig) {
|
||||
.id = APP_ID_BATTERY_CRITICAL,
|
||||
});
|
||||
}
|
||||
|
||||
static void prv_exit_critical(void) {
|
||||
app_manager_close_current_app(true);
|
||||
modal_manager_set_min_priority(ModalPriorityMin);
|
||||
}
|
||||
|
||||
static void prv_display_plugged(void *data) {
|
||||
if (!do_not_disturb_is_active()) {
|
||||
vibes_short_pulse();
|
||||
}
|
||||
battery_ui_display_plugged();
|
||||
|
||||
status_led_set(StatusLedState_Charging);
|
||||
}
|
||||
|
||||
static void prv_dismiss_plugged(void) {
|
||||
battery_ui_dismiss_modal();
|
||||
|
||||
status_led_set(StatusLedState_Off);
|
||||
}
|
||||
|
||||
static void prv_display_fully_charged(void *data) {
|
||||
battery_ui_display_fully_charged();
|
||||
status_led_set(StatusLedState_FullyCharged);
|
||||
}
|
||||
|
||||
static void prv_dismiss_fully_charged(void) {
|
||||
battery_ui_dismiss_modal();
|
||||
|
||||
status_led_set(StatusLedState_Off);
|
||||
}
|
||||
|
||||
// TODO PBL-39883: Replace w/ QUIRK_RESET_ON_SHUTDOWN_WHILE_CHARGING once arbitrary prefixes land
|
||||
#if PLATFORM_TINTIN || PLATFORM_SILK
|
||||
static void prv_shutdown(void *ignored) {
|
||||
battery_ui_handle_shut_down();
|
||||
}
|
||||
#else
|
||||
static void prv_enter_shutdown_charging(void *ignored) {
|
||||
app_manager_put_launch_app_event(&(AppLaunchEventConfig) {
|
||||
.id = APP_ID_SHUTDOWN_CHARGING,
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
// Internals
|
||||
|
||||
static void prv_transition(BatteryUIStateID next_state, void *data) {
|
||||
if (s_state != next_state) {
|
||||
// All self-transitions are internal.
|
||||
// A state's entry function is a its only valid action.
|
||||
// The exit function is only called on actual state changes.
|
||||
if (ui_states[s_state].exit) {
|
||||
ui_states[s_state].exit();
|
||||
}
|
||||
s_state = next_state;
|
||||
}
|
||||
if (ui_states[s_state].enter) {
|
||||
ui_states[s_state].enter(data);
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_is_valid_transition(BatteryUIStateID next_state) {
|
||||
const uint8_t count = ARRAY_LENGTH(ui_states[s_state].next_state);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (ui_states[s_state].next_state[i] == next_state) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static BatteryUIStateID prv_get_state(PreciseBatteryChargeState *state) {
|
||||
// TODO: Refactor?
|
||||
if (state->is_plugged) {
|
||||
// Don't use the PreciseBatteryChargeState definition of is_charging, as it maps to the
|
||||
// result of @see battery_charge_controller_thinks_we_are_charging instead of the actual
|
||||
// user-facing definition of charging.
|
||||
const uint32_t is_charging = battery_get_charge_state().is_charging;
|
||||
return is_charging ? BatteryCharging : BatteryFullyCharged;
|
||||
} else if (battery_monitor_critical_lockout()) {
|
||||
return BatteryCritical;
|
||||
} else if (low_power_is_active()) {
|
||||
return BatteryLowPower;
|
||||
} else if (ratio32_to_percent(state->charge_percent) <=
|
||||
battery_curve_get_percent_remaining(s_warning_points[0])) {
|
||||
return BatteryWarning;
|
||||
} else {
|
||||
return BatteryGood;
|
||||
}
|
||||
}
|
||||
|
||||
void battery_ui_handle_state_change_event(PreciseBatteryChargeState charge_state) {
|
||||
BatteryUIStateID next_state = prv_get_state(&charge_state);
|
||||
if (prv_is_valid_transition(next_state)) {
|
||||
prv_transition(next_state, &charge_state);
|
||||
}
|
||||
}
|
||||
|
||||
void battery_ui_handle_shut_down(void) {
|
||||
if (s_state != BatteryCharging) {
|
||||
enter_standby(RebootReasonCode_ShutdownMenuItem);
|
||||
} else {
|
||||
prv_transition(BatteryShutdownCharging, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void battery_ui_reset_fsm_for_tests(void) {
|
||||
s_state = BatteryGood;
|
||||
s_warning_points_index = -1;
|
||||
}
|
||||
127
src/fw/shell/normal/display_calibration_prompt.c
Normal file
127
src/fw/shell/normal/display_calibration_prompt.c
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#if PLATFORM_SPALDING
|
||||
|
||||
#include "display_calibration_prompt.h"
|
||||
|
||||
#include "applib/ui/dialogs/confirmation_dialog.h"
|
||||
#include "apps/system_apps/settings/settings_display_calibration.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "mfg/mfg_info.h"
|
||||
#include "mfg/mfg_serials.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "shell/prefs.h"
|
||||
#include "util/size.h"
|
||||
|
||||
// The calibration screen will be changing the screen offsets, so it's best that it remains on top
|
||||
// of most other modals (generic, alerts, etc) to prevent confusion about the screen's alignment.
|
||||
static const ModalPriority MODAL_PRIORITY = ModalPriorityCritical;
|
||||
|
||||
static void prv_calibrate_confirm_pop(ClickRecognizerRef recognizer, void *context) {
|
||||
i18n_free_all(context);
|
||||
confirmation_dialog_pop((ConfirmationDialog *)context);
|
||||
}
|
||||
|
||||
static void prv_calibrate_confirm_cb(ClickRecognizerRef recognizer, void *context) {
|
||||
settings_display_calibration_push(modal_manager_get_window_stack(MODAL_PRIORITY));
|
||||
prv_calibrate_confirm_pop(recognizer, context);
|
||||
}
|
||||
|
||||
static void prv_calibrate_click_config(void *context) {
|
||||
window_single_click_subscribe(BUTTON_ID_UP, prv_calibrate_confirm_cb);
|
||||
window_single_click_subscribe(BUTTON_ID_DOWN, prv_calibrate_confirm_pop);
|
||||
window_single_click_subscribe(BUTTON_ID_BACK, prv_calibrate_confirm_pop);
|
||||
}
|
||||
|
||||
static TimerID s_timer = TIMER_INVALID_ID;
|
||||
|
||||
static void prv_push_calibration_dialog(void *data) {
|
||||
shell_prefs_set_should_prompt_display_calibration(false);
|
||||
|
||||
ConfirmationDialog *confirmation_dialog = confirmation_dialog_create("Calibrate Prompt");
|
||||
Dialog *dialog = confirmation_dialog_get_dialog(confirmation_dialog);
|
||||
|
||||
dialog_set_text(dialog, i18n_get("Your screen may need calibration. Calibrate it now?",
|
||||
confirmation_dialog));
|
||||
dialog_set_background_color(dialog, GColorMediumAquamarine);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_GENERIC_PIN_TINY);
|
||||
confirmation_dialog_set_click_config_provider(confirmation_dialog,
|
||||
prv_calibrate_click_config);
|
||||
confirmation_dialog_push(confirmation_dialog,
|
||||
modal_manager_get_window_stack(MODAL_PRIORITY));
|
||||
}
|
||||
|
||||
static bool prv_display_has_user_offset(void) {
|
||||
GPoint display_offset = shell_prefs_get_display_offset();
|
||||
GPoint mfg_display_offset = mfg_info_get_disp_offsets();
|
||||
return (!gpoint_equal(&display_offset, &mfg_display_offset));
|
||||
}
|
||||
|
||||
static void prv_timer_callback(void *data) {
|
||||
new_timer_delete(s_timer);
|
||||
s_timer = TIMER_INVALID_ID;
|
||||
|
||||
// last check: make sure we need to display the prompt in case something changed in the
|
||||
// time that the timer was waiting.
|
||||
if (!shell_prefs_should_prompt_display_calibration()) {
|
||||
return;
|
||||
}
|
||||
|
||||
launcher_task_add_callback(prv_push_calibration_dialog, NULL);
|
||||
}
|
||||
|
||||
T_STATIC bool prv_is_known_misaligned_serial_number(const char *serial) {
|
||||
// Filter watches known to be misaligned based on the serial number. This is possible because
|
||||
// Serial numbers are represented as strings as described in:
|
||||
// https://pebbletechnology.atlassian.net/wiki/display/DEV/Hardware+Serial+Numbering
|
||||
// All watches of the same model produced from the same manufacturer on the same date, on the
|
||||
// same manufacturing line, will share the same first 8 characters of the serial number. In this
|
||||
// way, batches which are misaligned can be identified by a string comparison on these characters.
|
||||
//
|
||||
// NOTE: This also conveniently excludes test automation boards, so the dialog should not
|
||||
// appear during integration tests.
|
||||
const char *ranges[] = { "Q402445E" };
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(ranges); i++) {
|
||||
if (strncmp(serial, ranges[i], strlen(ranges[i])) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_is_potentially_misaligned_watch() {
|
||||
return !prv_display_has_user_offset() &&
|
||||
prv_is_known_misaligned_serial_number(mfg_get_serial_number());
|
||||
}
|
||||
|
||||
void display_calibration_prompt_show_if_needed(void) {
|
||||
if (!prv_is_potentially_misaligned_watch()) {
|
||||
shell_prefs_set_should_prompt_display_calibration(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell_prefs_should_prompt_display_calibration()) {
|
||||
s_timer = new_timer_create();
|
||||
const uint32_t prompt_delay_time_ms = MS_PER_SECOND * SECONDS_PER_MINUTE;
|
||||
new_timer_start(s_timer, prompt_delay_time_ms, prv_timer_callback, NULL, 0 /* flags */);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
19
src/fw/shell/normal/display_calibration_prompt.h
Normal file
19
src/fw/shell/normal/display_calibration_prompt.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void display_calibration_prompt_show_if_needed(void);
|
||||
43
src/fw/shell/normal/language_ui.c
Normal file
43
src/fw/shell/normal/language_ui.c
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 "language_ui.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "applib/ui/dialogs/simple_dialog.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
|
||||
static void prv_push_language_changed_dialog(void *data) {
|
||||
const char *lang_name = (const char *)data;
|
||||
SimpleDialog *simple_dialog = simple_dialog_create("LangFileChanged");
|
||||
Dialog *dialog = simple_dialog_get_dialog(simple_dialog);
|
||||
dialog_set_text(dialog, lang_name);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_GENERIC_CONFIRMATION_LARGE);
|
||||
dialog_set_background_color(dialog, GColorJaegerGreen);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_DEFAULT);
|
||||
simple_dialog_push(simple_dialog, modal_manager_get_window_stack(ModalPriorityAlert));
|
||||
// after dialog closes, launch the watchface
|
||||
watchface_launch_default(NULL);
|
||||
}
|
||||
|
||||
void language_ui_display_changed(const char *lang_name) {
|
||||
launcher_task_add_callback(prv_push_language_changed_dialog, (void *)lang_name);
|
||||
}
|
||||
20
src/fw/shell/normal/language_ui.h
Normal file
20
src/fw/shell/normal/language_ui.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Show a dialog indicating the language has changed
|
||||
void language_ui_display_changed(const char *lang_name);
|
||||
1178
src/fw/shell/normal/prefs.c
Normal file
1178
src/fw/shell/normal/prefs.c
Normal file
File diff suppressed because it is too large
Load Diff
40
src/fw/shell/normal/prefs_values.h.inc
Normal file
40
src/fw/shell/normal/prefs_values.h.inc
Normal file
@@ -0,0 +1,40 @@
|
||||
// Included by prefs.c to declare the list of preferences and their keys
|
||||
PREFS_MACRO(PREF_KEY_CLOCK_24H, s_clock_24h)
|
||||
PREFS_MACRO(PREF_KEY_CLOCK_TIMEZONE_SOURCE_IS_MANUAL, s_clock_timezone_source_is_manual)
|
||||
PREFS_MACRO(PREF_KEY_CLOCK_PHONE_TIMEZONE_ID, s_clock_phone_timezone_id)
|
||||
PREFS_MACRO(PREF_KEY_UNITS_DISTANCE, s_units_distance)
|
||||
PREFS_MACRO(PREF_KEY_BACKLIGHT_ENABLED, s_backlight_enabled)
|
||||
PREFS_MACRO(PREF_KEY_BACKLIGHT_AMBIENT_SENSOR_ENABLED, s_backlight_ambient_sensor_enabled)
|
||||
PREFS_MACRO(PREF_KEY_BACKLIGHT_TIMEOUT_MS, s_backlight_timeout_ms)
|
||||
PREFS_MACRO(PREF_KEY_BACKLIGHT_INTENSITY, s_backlight_intensity)
|
||||
PREFS_MACRO(PREF_KEY_BACKLIGHT_MOTION, s_backlight_motion_enabled)
|
||||
PREFS_MACRO(PREF_KEY_STATIONARY, s_stationary_mode_enabled)
|
||||
PREFS_MACRO(PREF_KEY_DEFAULT_WORKER, s_default_worker)
|
||||
PREFS_MACRO(PREF_KEY_TEXT_STYLE, s_text_style)
|
||||
PREFS_MACRO(PREF_KEY_LANG_ENGLISH, s_language_english)
|
||||
PREFS_MACRO(PREF_KEY_QUICK_LAUNCH_UP, s_quick_launch_up)
|
||||
PREFS_MACRO(PREF_KEY_QUICK_LAUNCH_DOWN, s_quick_launch_down)
|
||||
PREFS_MACRO(PREF_KEY_QUICK_LAUNCH_SELECT, s_quick_launch_select)
|
||||
PREFS_MACRO(PREF_KEY_QUICK_LAUNCH_BACK, s_quick_launch_back)
|
||||
PREFS_MACRO(PREF_KEY_QUICK_LAUNCH_SETUP_OPENED, s_quick_launch_setup_opened)
|
||||
PREFS_MACRO(PREF_KEY_DEFAULT_WATCHFACE, s_default_watchface)
|
||||
PREFS_MACRO(PREF_KEY_WELCOME_VERSION, s_welcome_version)
|
||||
#if CAPABILITY_HAS_HEALTH_TRACKING
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_PREFERENCES, s_activity_preferences)
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_ACTIVATED_TIMESTAMP, s_activity_activation_timestamp)
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_ACTIVATION_DELAY_INSIGHT, s_activity_activation_delay_insight)
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_HEALTH_APP_OPENED, s_activity_prefs_health_app_opened)
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_WORKOUT_APP_OPENED, s_activity_prefs_workout_app_opened)
|
||||
PREFS_MACRO(PREF_KEY_ALARMS_APP_OPENED, s_alarms_app_opened)
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_HRM_PREFERENCES, s_activity_hrm_preferences)
|
||||
PREFS_MACRO(PREF_KEY_ACTIVITY_HEART_RATE_PREFERENCES, s_activity_hr_preferences)
|
||||
#endif
|
||||
#if PLATFORM_SPALDING
|
||||
PREFS_MACRO(PREF_KEY_DISPLAY_USER_OFFSET, s_display_user_offset)
|
||||
PREFS_MACRO(PREF_KEY_SHOULD_PROMPT_DISPLAY_CALIBRATION, s_should_prompt_display_calibration)
|
||||
#endif
|
||||
#if CAPABILITY_HAS_TIMELINE_PEEK
|
||||
PREFS_MACRO(PREF_KEY_TIMELINE_SETTINGS_OPENED, s_timeline_settings_opened)
|
||||
PREFS_MACRO(PREF_KEY_TIMELINE_PEEK_ENABLED, s_timeline_peek_enabled)
|
||||
PREFS_MACRO(PREF_KEY_TIMELINE_PEEK_BEFORE_TIME_M, s_timeline_peek_before_time_m)
|
||||
#endif
|
||||
28
src/fw/shell/normal/quick_launch.h
Normal file
28
src/fw/shell/normal/quick_launch.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "drivers/button_id.h"
|
||||
#include "process_management/app_install_types.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
bool quick_launch_is_enabled(ButtonId button);
|
||||
AppInstallId quick_launch_get_app(ButtonId button);
|
||||
void quick_launch_set_app(ButtonId button, AppInstallId app_id);
|
||||
void quick_launch_set_enabled(ButtonId button, bool enabled);
|
||||
void quick_launch_set_quick_launch_setup_opened(uint8_t version);
|
||||
uint8_t quick_launch_get_quick_launch_setup_opened(void);
|
||||
125
src/fw/shell/normal/shell.c
Normal file
125
src/fw/shell/normal/shell.c
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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/shell.h"
|
||||
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_install_types.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "services/common/compositor/compositor_transitions.h"
|
||||
|
||||
#define WATCHFACE_SHUTTER_COLOR GColorWhite
|
||||
#define HEALTH_SHUTTER_COLOR PBL_IF_COLOR_ELSE(GColorBlack, GColorWhite)
|
||||
#define ACTION_SHUTTER_COLOR PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite)
|
||||
|
||||
static const CompositorTransition *prv_get_watchface_compositor_animation(
|
||||
CompositorTransitionDirection direction) {
|
||||
return PBL_IF_RECT_ELSE(compositor_shutter_transition_get(direction, WATCHFACE_SHUTTER_COLOR),
|
||||
compositor_port_hole_transition_app_get(direction));
|
||||
}
|
||||
|
||||
static const CompositorTransition *prv_get_health_compositor_animation(
|
||||
CompositorTransitionDirection direction) {
|
||||
return PBL_IF_RECT_ELSE(compositor_shutter_transition_get(direction, HEALTH_SHUTTER_COLOR),
|
||||
compositor_port_hole_transition_app_get(direction));
|
||||
}
|
||||
|
||||
static const CompositorTransition *prv_get_action_compositor_animation(
|
||||
CompositorTransitionDirection direction) {
|
||||
return PBL_IF_RECT_ELSE(compositor_shutter_transition_get(direction, ACTION_SHUTTER_COLOR),
|
||||
NULL);
|
||||
}
|
||||
|
||||
const CompositorTransition *shell_get_watchface_compositor_animation(
|
||||
bool watchface_is_destination) {
|
||||
const CompositorTransitionDirection direction = watchface_is_destination ?
|
||||
CompositorTransitionDirectionLeft : CompositorTransitionDirectionRight;
|
||||
return prv_get_watchface_compositor_animation(direction);
|
||||
}
|
||||
|
||||
static const CompositorTransition *prv_app_launcher_transition_animation(
|
||||
CompositorTransitionDirection direction) {
|
||||
const bool app_is_destination = (direction == CompositorTransitionDirectionRight);
|
||||
return PBL_IF_RECT_ELSE(compositor_launcher_app_transition_get(app_is_destination),
|
||||
compositor_port_hole_transition_app_get(direction));
|
||||
}
|
||||
|
||||
const CompositorTransition *shell_get_close_compositor_animation(AppInstallId current_app_id,
|
||||
AppInstallId next_app_id) {
|
||||
const CompositorTransition *res = NULL;
|
||||
AppInstallEntry *app_entry = kernel_zalloc_check(sizeof(AppInstallEntry));
|
||||
|
||||
if (app_install_get_entry_for_install_id(next_app_id, app_entry) &&
|
||||
app_install_entry_is_watchface(app_entry)) {
|
||||
if (current_app_id == APP_ID_LAUNCHER_MENU) {
|
||||
res = prv_get_watchface_compositor_animation(CompositorTransitionDirectionLeft);
|
||||
goto done;
|
||||
} else if (current_app_id == APP_ID_HEALTH_APP) {
|
||||
res = prv_get_health_compositor_animation(CompositorTransitionDirectionDown);
|
||||
goto done;
|
||||
} else {
|
||||
res = prv_get_action_compositor_animation(CompositorTransitionDirectionLeft);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (next_app_id == APP_ID_LAUNCHER_MENU) {
|
||||
res = prv_app_launcher_transition_animation(CompositorTransitionDirectionLeft);
|
||||
goto done;
|
||||
}
|
||||
|
||||
// If we get here, we don't use a compositor animation for the transition
|
||||
|
||||
done:
|
||||
kernel_free(app_entry);
|
||||
return res;
|
||||
}
|
||||
|
||||
const CompositorTransition *shell_get_open_compositor_animation(AppInstallId current_app_id,
|
||||
AppInstallId next_app_id) {
|
||||
const CompositorTransition *res = NULL;
|
||||
AppInstallEntry *app_entry = kernel_zalloc_check(sizeof(AppInstallEntry));
|
||||
|
||||
if (app_install_get_entry_for_install_id(current_app_id, app_entry)) {
|
||||
if (app_install_entry_is_watchface(app_entry)) {
|
||||
if (next_app_id == APP_ID_LAUNCHER_MENU) {
|
||||
res = prv_get_watchface_compositor_animation(CompositorTransitionDirectionRight);
|
||||
goto done;
|
||||
} else if (next_app_id == APP_ID_HEALTH_APP) {
|
||||
res = prv_get_health_compositor_animation(CompositorTransitionDirectionUp);
|
||||
goto done;
|
||||
}
|
||||
} else if ((current_app_id == APP_ID_HEALTH_APP) &&
|
||||
app_install_get_entry_for_install_id(next_app_id, app_entry) &&
|
||||
app_install_entry_is_watchface(app_entry)) {
|
||||
res = prv_get_health_compositor_animation(CompositorTransitionDirectionDown);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_app_id == APP_ID_LAUNCHER_MENU) {
|
||||
res = prv_app_launcher_transition_animation(CompositorTransitionDirectionRight);
|
||||
goto done;
|
||||
}
|
||||
|
||||
// If we get here, we don't use a compositor animation for the transition
|
||||
|
||||
done:
|
||||
kernel_free(app_entry);
|
||||
return res;
|
||||
}
|
||||
204
src/fw/shell/normal/shell_event_loop.c
Normal file
204
src/fw/shell/normal/shell_event_loop.c
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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 <kernel/events.h>
|
||||
#include "shell/shell_event_loop.h"
|
||||
#include "shell/prefs_private.h"
|
||||
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "apps/system_apps/app_fetch_ui.h"
|
||||
#include "apps/system_apps/settings/settings_quick_launch.h"
|
||||
#include "apps/system_apps/timeline/timeline.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "popups/alarm_popup.h"
|
||||
#include "popups/bluetooth_pairing_ui.h"
|
||||
#include "popups/notifications/notification_window.h"
|
||||
#include "popups/timeline/peek.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/process_manager.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/bluetooth/bluetooth_persistent_storage.h"
|
||||
#include "services/common/shared_prf_storage/shared_prf_storage.h"
|
||||
#include "services/normal/activity/activity.h"
|
||||
#include "services/normal/activity/workout_service.h"
|
||||
#include "services/normal/app_inbox_service.h"
|
||||
#include "services/normal/app_outbox_service.h"
|
||||
#include "services/normal/music.h"
|
||||
#include "services/normal/music_endpoint.h"
|
||||
#include "services/normal/notifications/do_not_disturb.h"
|
||||
#include "services/normal/stationary.h"
|
||||
#include "services/normal/timeline/event.h"
|
||||
#include "shell/normal/app_idle_timeout.h"
|
||||
#include "shell/normal/battery_ui.h"
|
||||
#include "shell/normal/display_calibration_prompt.h"
|
||||
#include "shell/normal/quick_launch.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "shell/normal/welcome.h"
|
||||
#include "shell/prefs.h"
|
||||
#include "system/logging.h"
|
||||
|
||||
|
||||
extern void shell_prefs_init(void);
|
||||
|
||||
void shell_event_loop_init(void) {
|
||||
shell_prefs_init();
|
||||
#if PLATFORM_SPALDING
|
||||
shell_prefs_display_offset_init();
|
||||
display_calibration_prompt_show_if_needed();
|
||||
#endif
|
||||
notification_window_service_init();
|
||||
app_inbox_service_init();
|
||||
app_outbox_service_init();
|
||||
app_message_sender_init();
|
||||
watchface_init();
|
||||
timeline_peek_init();
|
||||
#if CAPABILITY_HAS_HEALTH_TRACKING
|
||||
// Start activity tracking if enabled
|
||||
if (activity_prefs_tracking_is_enabled()) {
|
||||
activity_start_tracking(false /*test_mode*/);
|
||||
}
|
||||
workout_service_init();
|
||||
#endif
|
||||
|
||||
bool factory_reset_or_first_use = !shared_prf_storage_get_getting_started_complete();
|
||||
// We are almost done booting, welcome the user if applicable. This _must_ occur before setting
|
||||
// the getting started completed below.
|
||||
welcome_push_notification(factory_reset_or_first_use);
|
||||
if (factory_reset_or_first_use) {
|
||||
bt_persistent_storage_set_unfaithful(true);
|
||||
}
|
||||
|
||||
// As soon as we boot normally for the first time, we've therefore completed first use mode and
|
||||
// we don't need to go through it again until we factory reset.
|
||||
shared_prf_storage_set_getting_started_complete(true /* complete */);
|
||||
}
|
||||
|
||||
void shell_event_loop_handle_event(PebbleEvent *e) {
|
||||
switch (e->type) {
|
||||
case PEBBLE_APP_FETCH_REQUEST_EVENT:
|
||||
app_manager_handle_app_fetch_request_event(&e->app_fetch_request);
|
||||
return;
|
||||
|
||||
case PEBBLE_ALARM_CLOCK_EVENT:
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_ALARM_SOUNDED_COUNT, AnalyticsClient_System);
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Alarm event in the shell event loop");
|
||||
stationary_wake_up();
|
||||
alarm_popup_push_window(&e->alarm_clock);
|
||||
return;
|
||||
|
||||
case PEBBLE_BT_PAIRING_EVENT:
|
||||
bluetooth_pairing_ui_handle_event(&e->bluetooth.pair);
|
||||
return;
|
||||
|
||||
case PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT:
|
||||
if (e->app_focus.in_focus) {
|
||||
app_idle_timeout_resume();
|
||||
} else {
|
||||
app_idle_timeout_pause();
|
||||
}
|
||||
return;
|
||||
|
||||
case PEBBLE_SYS_NOTIFICATION_EVENT:
|
||||
// This handles incoming Notifications and actions on Notifications and Reminders
|
||||
notification_window_handle_notification(&e->sys_notification);
|
||||
return;
|
||||
|
||||
case PEBBLE_CALENDAR_EVENT:
|
||||
do_not_disturb_handle_calendar_event(&e->calendar);
|
||||
return;
|
||||
|
||||
case PEBBLE_TIMELINE_PEEK_EVENT:
|
||||
timeline_peek_handle_peek_event(&e->timeline_peek);
|
||||
return;
|
||||
|
||||
case PEBBLE_BLOBDB_EVENT:
|
||||
{
|
||||
// Calendar should only handle pin_db events
|
||||
PebbleBlobDBEvent *blobdb_event = &e->blob_db;
|
||||
if (blobdb_event->db_id == BlobDBIdPins) {
|
||||
timeline_event_handle_blobdb_event();
|
||||
} else if (blobdb_event->db_id == BlobDBIdPrefs) {
|
||||
prefs_private_handle_blob_db_event(blobdb_event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case PEBBLE_DO_NOT_DISTURB_EVENT:
|
||||
notification_window_handle_dnd_event(&e->do_not_disturb);
|
||||
return;
|
||||
|
||||
case PEBBLE_REMINDER_EVENT:
|
||||
// This handles incoming Reminders
|
||||
notification_window_handle_reminder(&e->reminder);
|
||||
return;
|
||||
|
||||
case PEBBLE_BATTERY_STATE_CHANGE_EVENT:
|
||||
battery_ui_handle_state_change_event(e->battery_state.new_state);
|
||||
return;
|
||||
|
||||
case PEBBLE_COMM_SESSION_EVENT:
|
||||
music_endpoint_handle_mobile_app_event(&e->bluetooth.comm_session_event);
|
||||
return;
|
||||
|
||||
// Sent by the comm layer once we get a response from the mobile app to a phone version request
|
||||
case PEBBLE_REMOTE_APP_INFO_EVENT:
|
||||
music_endpoint_handle_mobile_app_info_event(&e->bluetooth.app_info_event);
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_PHONE_APP_INFO_COUNT, AnalyticsClient_System);
|
||||
return;
|
||||
|
||||
case PEBBLE_MEDIA_EVENT:
|
||||
if (e->media.playback_state == MusicPlayStatePlaying) {
|
||||
app_install_mark_prioritized(APP_ID_MUSIC, true /* can_expire */);
|
||||
}
|
||||
return;
|
||||
|
||||
case PEBBLE_HEALTH_SERVICE_EVENT:
|
||||
workout_service_health_event_handler(&e->health_event);
|
||||
return;
|
||||
|
||||
case PEBBLE_ACTIVITY_EVENT:
|
||||
workout_service_activity_event_handler(&e->activity_event);
|
||||
return;
|
||||
|
||||
case PEBBLE_WORKOUT_EVENT: {
|
||||
// If a workout is ongoing, keep the app at the top of the launcher.
|
||||
// When a workout is stopped it will return to it's normal position after the
|
||||
// default timeout.
|
||||
PebbleWorkoutEvent *workout_e = &e->workout;
|
||||
bool can_expire = true;
|
||||
switch (workout_e->type) {
|
||||
case PebbleWorkoutEvent_Started:
|
||||
case PebbleWorkoutEvent_Paused:
|
||||
can_expire = false;
|
||||
break;
|
||||
case PebbleWorkoutEvent_Stopped:
|
||||
can_expire = true;
|
||||
break;
|
||||
case PebbleWorkoutEvent_FrontendOpened:
|
||||
case PebbleWorkoutEvent_FrontendClosed:
|
||||
break;
|
||||
}
|
||||
app_install_mark_prioritized(APP_ID_WORKOUT, can_expire);
|
||||
workout_service_workout_event_handler(workout_e);
|
||||
return;
|
||||
}
|
||||
default:
|
||||
break; // don't care
|
||||
}
|
||||
}
|
||||
|
||||
172
src/fw/shell/normal/shutdown_charging.c
Normal file
172
src/fw/shell/normal/shutdown_charging.c
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "applib/app.h"
|
||||
#include "applib/app_timer.h"
|
||||
#include "applib/battery_state_service.h"
|
||||
#include "applib/ui/dialogs/simple_dialog.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/util/standby.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/runlevel.h"
|
||||
#include "services/common/status_led.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/reboot_reason.h"
|
||||
#include "system/reset.h"
|
||||
|
||||
static const uint32_t CHARGER_DISCONNECT_TIMEOUT_MS = 3000;
|
||||
|
||||
typedef enum DialogState {
|
||||
DialogState_Uninitialized = 0,
|
||||
DialogState_Charging,
|
||||
DialogState_FullyCharged,
|
||||
} DialogState;
|
||||
|
||||
|
||||
struct AppData {
|
||||
SimpleDialog *dialog;
|
||||
AppTimer *poweroff_timer;
|
||||
DialogState last_dialog_state;
|
||||
bool was_plugged;
|
||||
};
|
||||
|
||||
static void prv_reboot_on_click(ClickRecognizerRef recognizer, void *data) {
|
||||
// Don't try to return to normal functioning; just reboot the watch. The user
|
||||
// thinks the watch is already off anyway.
|
||||
RebootReason reboot_reason = { RebootReasonCode_ShutdownMenuItem };
|
||||
reboot_reason_set(&reboot_reason);
|
||||
system_reset();
|
||||
}
|
||||
|
||||
static void prv_config_provider(void *context) {
|
||||
for (int i = 0; i < NUM_BUTTONS; ++i) {
|
||||
window_long_click_subscribe(i, 0, prv_reboot_on_click, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_power_off_timer_expired(void *data) {
|
||||
enter_standby(RebootReasonCode_ShutdownMenuItem);
|
||||
}
|
||||
|
||||
static void prv_battery_state_handler(BatteryChargeState charge) {
|
||||
struct AppData *data = app_state_get_user_data();
|
||||
if (charge.is_plugged && !data->was_plugged) {
|
||||
app_timer_cancel(data->poweroff_timer);
|
||||
} else if (!charge.is_plugged && data->was_plugged) {
|
||||
data->poweroff_timer = app_timer_register(
|
||||
CHARGER_DISCONNECT_TIMEOUT_MS, prv_power_off_timer_expired, NULL);
|
||||
}
|
||||
|
||||
DialogState next_dialog_state = DialogState_Uninitialized;
|
||||
|
||||
if (charge.is_charging) {
|
||||
next_dialog_state = DialogState_Charging;
|
||||
} else if (charge.is_plugged) {
|
||||
next_dialog_state = DialogState_FullyCharged;
|
||||
} else {
|
||||
// Unplugged. We'll be shutting down in a couple seconds if the user doesn't
|
||||
// plug the charger back in, so don't change the dialog.
|
||||
next_dialog_state = data->last_dialog_state;
|
||||
}
|
||||
|
||||
Dialog *dialog = simple_dialog_get_dialog(data->dialog);
|
||||
|
||||
if (next_dialog_state != data->last_dialog_state) {
|
||||
// Setting the dialog icon to itself restarts the animation, which looks
|
||||
// bad, so we want to avoid that if we can help it.
|
||||
switch (next_dialog_state) {
|
||||
case DialogState_FullyCharged:
|
||||
dialog_set_text(dialog, i18n_get("Fully Charged", data));
|
||||
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_FULL_LARGE_INVERTED);
|
||||
break;
|
||||
case DialogState_Charging:
|
||||
default:
|
||||
dialog_set_text(dialog, i18n_get("Charging", data));
|
||||
dialog_set_icon(dialog, RESOURCE_ID_BATTERY_ICON_CHARGING_LARGE_INVERTED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (charge.is_plugged) {
|
||||
if (charge.is_charging) {
|
||||
status_led_set(StatusLedState_Charging);
|
||||
} else {
|
||||
status_led_set(StatusLedState_FullyCharged);
|
||||
}
|
||||
} else {
|
||||
status_led_set(StatusLedState_Off);
|
||||
}
|
||||
|
||||
data->was_plugged = charge.is_plugged;
|
||||
data->last_dialog_state = next_dialog_state;
|
||||
}
|
||||
|
||||
static void prv_handle_init(void) {
|
||||
struct AppData *data = app_malloc_check(sizeof(struct AppData));
|
||||
*data = (struct AppData){};
|
||||
app_state_set_user_data(data);
|
||||
|
||||
data->dialog = simple_dialog_create(WINDOW_NAME("Shutdown Charging"));
|
||||
Dialog *dialog = simple_dialog_get_dialog(data->dialog);
|
||||
dialog_set_background_color(dialog, GColorBlack);
|
||||
dialog_set_text_color(dialog, GColorWhite);
|
||||
window_set_click_config_provider(&dialog->window, prv_config_provider);
|
||||
|
||||
// The assumption is that this app is launched when the charger is connected
|
||||
// and the shutdown menu item is selected.
|
||||
data->was_plugged = true;
|
||||
data->last_dialog_state = DialogState_Uninitialized;
|
||||
battery_state_service_subscribe(prv_battery_state_handler);
|
||||
// Handle the edge-case where the charger is disconnected between the user
|
||||
// selecting shut down and this app subscribing to battery state events.
|
||||
// Also set the initial battery charge level.
|
||||
prv_battery_state_handler(battery_state_service_peek());
|
||||
|
||||
app_simple_dialog_push(data->dialog);
|
||||
// TODO: have the runlevel machinery disable bluetooth and worker.
|
||||
services_set_runlevel(RunLevel_BareMinimum);
|
||||
worker_manager_disable();
|
||||
}
|
||||
|
||||
|
||||
static void s_main(void) {
|
||||
prv_handle_init();
|
||||
app_event_loop();
|
||||
}
|
||||
|
||||
const PebbleProcessMd* shutdown_charging_get_app_info(void) {
|
||||
static const PebbleProcessMdSystem s_app_md = {
|
||||
.common = {
|
||||
.main_func = s_main,
|
||||
.visibility = ProcessVisibilityHidden,
|
||||
// UUID: 48fa66c4-4e6f-4b32-bf75-a16e12d630c3
|
||||
.uuid = {0x48, 0xfa, 0x66, 0xc4, 0x4e, 0x6f, 0x4b, 0x32,
|
||||
0xbf, 0x75, 0xa1, 0x6e, 0x12, 0xd6, 0x30, 0xc3},
|
||||
},
|
||||
.name = "Shutdown Charging",
|
||||
};
|
||||
return (const PebbleProcessMd*) &s_app_md;
|
||||
}
|
||||
621
src/fw/shell/normal/system_app_registry_list.json
Normal file
621
src/fw/shell/normal/system_app_registry_list.json
Normal file
@@ -0,0 +1,621 @@
|
||||
{
|
||||
"warning_one": [
|
||||
" 1. DO NOT CHANGE OR REUSE THE ID OF ANY APPLICATION IN THE LIST ",
|
||||
" 2. Read the directions "
|
||||
],
|
||||
"directions": [
|
||||
" - The System Apps are the applications that are actually coded",
|
||||
" into the firmware with static PebbleProcessMd's. ",
|
||||
" - The Resource Apps are the applications that are stored in ",
|
||||
" the resource pack included with the firmware. ",
|
||||
" - The section 'system_apps' only lists the PebbleProcessMd* ",
|
||||
" functions ",
|
||||
" - Resource app entry requires a UUID, then bin_resource_id ",
|
||||
" then an icon_resource_id ",
|
||||
" - To enable only certain applications on a certain ",
|
||||
" add the particular DEFINE variable into the 'ifdefs' field ",
|
||||
" Doing so will add that application to the generated list ",
|
||||
" - To disable applications entirely, add a 'DISABLED' define to",
|
||||
" the list of defines for the application "
|
||||
],
|
||||
"warning_two": [
|
||||
" 1. DO NOT CHANGE OR REUSE THE ID OF ANY APPLICATION IN THE LIST ",
|
||||
" 2. Read the directions "
|
||||
],
|
||||
"system_apps": [
|
||||
{
|
||||
"id": -69,
|
||||
"enum": "TICTOC",
|
||||
"md_fn": "tictoc_get_app_info"
|
||||
},
|
||||
{
|
||||
"id": -98,
|
||||
"enum": "KICKSTART",
|
||||
"md_fn": "kickstart_get_app_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -2,
|
||||
"enum": "LOW_POWER_FACE",
|
||||
"md_fn": "low_power_face_get_app_info"
|
||||
},
|
||||
{
|
||||
"id": -7,
|
||||
"enum": "SETTINGS",
|
||||
"md_fn": "settings_get_app_info",
|
||||
"color_argb8": "GColorLightGrayARGB8"
|
||||
},
|
||||
{
|
||||
"id": -3,
|
||||
"enum": "MUSIC",
|
||||
"md_fn": "music_app_get_info",
|
||||
"color_argb8": "GColorOrangeARGB8"
|
||||
},
|
||||
{
|
||||
"id": -4,
|
||||
"enum": "NOTIFICATIONS",
|
||||
"md_fn": "notifications_app_get_info",
|
||||
"color_argb8": "GColorSunsetOrangeARGB8"
|
||||
},
|
||||
{
|
||||
"id": -5,
|
||||
"enum": "ALARMS",
|
||||
"md_fn": "alarms_app_get_info",
|
||||
"color_argb8": "GColorJaegerGreenARGB8"
|
||||
},
|
||||
{
|
||||
"id": -6,
|
||||
"enum": "WATCHFACES",
|
||||
"md_fn": "watchfaces_get_app_info",
|
||||
"color_argb8": "GColorJazzberryJamARGB8"
|
||||
},
|
||||
{
|
||||
"id": -9,
|
||||
"enum": "QUICK_LAUNCH_SETUP",
|
||||
"md_fn": "quick_launch_setup_get_app_info"
|
||||
},
|
||||
{
|
||||
"id": -10,
|
||||
"enum": "TIMELINE",
|
||||
"md_fn": "timeline_get_app_info"
|
||||
},
|
||||
{
|
||||
"id": -12,
|
||||
"enum": "BOUNCING_BOX_DEMO",
|
||||
"md_fn": "bouncing_box_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -13,
|
||||
"enum": "PEBBLE_COLORS",
|
||||
"md_fn": "pebble_colors_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -14,
|
||||
"enum": "PEBBLE_SHAPES",
|
||||
"md_fn": "pebble_shapes_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -17,
|
||||
"enum": "MOVABLE_LINE",
|
||||
"md_fn": "movable_line_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -18,
|
||||
"enum": "GFX_TESTS",
|
||||
"md_fn": "gfx_tests_get_app_info",
|
||||
"ifdefs": ["PERFORMANCE_TESTS"]
|
||||
},
|
||||
{
|
||||
"id": -19,
|
||||
"enum": "TEST_ARGS_RECEIVER",
|
||||
"md_fn": "test_args_receiver_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -20,
|
||||
"enum": "TEST_ARGS_SENDER",
|
||||
"md_fn": "test_args_sender_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -21,
|
||||
"enum": "FLASH_DIAGNOSTIC",
|
||||
"md_fn": "flash_diagnostic_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -22,
|
||||
"enum": "FS_RESOURCES",
|
||||
"md_fn": "fs_resources_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -23,
|
||||
"enum": "EXIT",
|
||||
"md_fn": "exit_app_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -25,
|
||||
"enum": "APP_HEAP_DEMO",
|
||||
"md_fn": "app_heap_demo_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -26,
|
||||
"enum": "DATA_LOGGING_TEST",
|
||||
"md_fn": "data_logging_test_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -27,
|
||||
"enum": "KILL_BT",
|
||||
"md_fn": "kill_bt_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -28,
|
||||
"enum": "TRIGGER_ALARM",
|
||||
"md_fn": "trigger_alarm_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -29,
|
||||
"enum": "ANIMATED_DEMO",
|
||||
"md_fn": "animated_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -30,
|
||||
"enum": "GRENADE_LAUNCHER",
|
||||
"md_fn": "grenade_launcher_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -31,
|
||||
"enum": "TEXT_LAYOUT",
|
||||
"md_fn": "text_layout_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -33,
|
||||
"enum": "MENU",
|
||||
"md_fn": "menu_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -34,
|
||||
"enum": "SIMPLE_MENU",
|
||||
"md_fn": "simple_menu_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -35,
|
||||
"enum": "SCROLL",
|
||||
"md_fn": "scroll_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -36,
|
||||
"enum": "CLICK",
|
||||
"md_fn": "click_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -37,
|
||||
"enum": "PROGRESS",
|
||||
"md_fn": "progress_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -38,
|
||||
"enum": "NUMBER_FIELD",
|
||||
"md_fn": "number_field_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -41,
|
||||
"enum": "EVENT_SERVICE",
|
||||
"md_fn": "event_service_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -43,
|
||||
"enum": "PERSIST",
|
||||
"md_fn": "persist_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -44,
|
||||
"enum": "TIMER",
|
||||
"md_fn": "timer_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -45,
|
||||
"enum": "TEST_SYS_TIMER",
|
||||
"md_fn": "test_sys_timer_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -46,
|
||||
"enum": "TEST_CORE_DUMP",
|
||||
"md_fn": "test_core_dump_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -47,
|
||||
"enum": "FLASH_PROF",
|
||||
"md_fn": "flash_prof_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -48,
|
||||
"enum": "TEST_BLUETOOTH",
|
||||
"md_fn": "test_bluetooth_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -50,
|
||||
"enum": "VIBE_AND_LOGS",
|
||||
"md_fn": "vibe_and_logs_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -51,
|
||||
"enum": "MENU_OVERFLOW",
|
||||
"md_fn": "menu_overflow_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -54,
|
||||
"enum": "LAUNCHER_MENU",
|
||||
"md_fn": "launcher_menu_app_get_app_info"
|
||||
},
|
||||
{
|
||||
"id": -55,
|
||||
"enum": "TEXT_CLIPPING",
|
||||
"md_fn": "text_clipping_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -56,
|
||||
"enum": "LIGHT_CONFIG",
|
||||
"md_fn": "light_config_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -57,
|
||||
"enum": "STROKE_WIDTH",
|
||||
"md_fn": "stroke_width_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -58,
|
||||
"enum": "AMB_LIGHT_READ",
|
||||
"md_fn": "ambient_light_reading_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -59,
|
||||
"enum": "WEATHER",
|
||||
"md_fn": "weather_app_get_info",
|
||||
"color_argb8": "GColorBlueMoonARGB8",
|
||||
"target_platforms":[
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -95,
|
||||
"enum": "WORKOUT",
|
||||
"md_fn": "workout_app_get_info",
|
||||
"color_argb8": "GColorYellowARGB8",
|
||||
"target_platforms": [
|
||||
"silk"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -60,
|
||||
"enum": "SHUTDOWN_CHARGING",
|
||||
"md_fn": "shutdown_charging_get_app_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -61,
|
||||
"enum": "SWAP_LAYER",
|
||||
"md_fn": "swap_layer_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -62,
|
||||
"enum": "BATTERY_CRITICAL",
|
||||
"md_fn": "battery_critical_get_app_info"
|
||||
},
|
||||
{
|
||||
"id": -63,
|
||||
"enum": "TEXT_SPACING",
|
||||
"md_fn": "text_spacing_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -64,
|
||||
"enum": "KINO_LAYER",
|
||||
"md_fn": "kino_layer_demo_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -65,
|
||||
"enum": "DIALOGS",
|
||||
"md_fn": "dialogs_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -66,
|
||||
"enum": "STATUSBAR",
|
||||
"md_fn": "statusbar_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -68,
|
||||
"enum": "MORPH_SQUARE",
|
||||
"md_fn": "morph_square_demo_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -70,
|
||||
"enum": "MENU_RIGHT_ICON",
|
||||
"md_fn": "menu_layer_right_icon_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -71,
|
||||
"enum": "VIBE_STRENGTH",
|
||||
"md_fn": "vibe_strength_demo_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -72,
|
||||
"enum": "ACTION_MENU",
|
||||
"md_fn": "action_menu_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -73,
|
||||
"enum": "OPTION_MENU",
|
||||
"md_fn": "option_menu_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -74,
|
||||
"enum": "PROFILE_MUTEXES",
|
||||
"md_fn": "profile_mutexes_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS", "DISABLED"]
|
||||
},
|
||||
{
|
||||
"id": -75,
|
||||
"enum": "DEADLOCK",
|
||||
"md_fn": "deadlock_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -76,
|
||||
"enum": "TIMELINE_PINS",
|
||||
"md_fn": "timeline_pins_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -77,
|
||||
"enum": "TEXT_FLOW",
|
||||
"md_fn": "text_flow_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -78,
|
||||
"enum": "MENU_ROUND",
|
||||
"md_fn": "menu_round_app_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"spalding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -79,
|
||||
"enum": "ACTIVITY_DEMO",
|
||||
"md_fn": "activity_demo_get_app_info",
|
||||
"ifdefs": ["SHOW_ACTIVITY_DEMO"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -80,
|
||||
"enum": "ACTIVITY_TEST",
|
||||
"md_fn": "activity_test_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -81,
|
||||
"enum": "DOUBLE_TAP_TEST",
|
||||
"md_fn": "double_tap_test_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"]
|
||||
},
|
||||
{
|
||||
"id": -82,
|
||||
"enum": "HEALTH_APP",
|
||||
"md_fn": "health_app_get_info",
|
||||
"color_argb8": "GColorSunsetOrangeARGB8",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -83,
|
||||
"enum": "SEND_TEXT",
|
||||
"md_fn": "send_text_app_get_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -84,
|
||||
"enum": "VIBE_SCORE",
|
||||
"md_fn": "vibe_score_demo_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -86,
|
||||
"enum": "IDL",
|
||||
"md_fn": "idl_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -87,
|
||||
"enum": "TEMPERATURE_DEMO",
|
||||
"md_fn": "temperature_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -88,
|
||||
"enum": "GDRAWMASK_DEMO",
|
||||
"md_fn": "gdrawmask_demo_get_app_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -90,
|
||||
"enum": "REMINDERS",
|
||||
"md_fn": "reminder_app_get_info",
|
||||
"color_argb8": "GColorChromeYellowARGB8",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
],
|
||||
"ifdefs": ["CAPABILITY_HAS_MICROPHONE=1"]
|
||||
},
|
||||
{
|
||||
"id": -91,
|
||||
"enum": "MPU_TEST",
|
||||
"md_fn": "test_mpu_cache_get_info",
|
||||
"ifdefs": ["ENABLE_TEST_APPS"],
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -92,
|
||||
"enum": "QUIET_TIME_TOGGLE",
|
||||
"md_fn": "quiet_time_toggle_get_app_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -94,
|
||||
"enum": "MOTION_BACKLIGHT_TOGGLE",
|
||||
"md_fn": "motion_backlight_toggle_get_app_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -93,
|
||||
"enum": "AIRPLANE_MODE_TOGGLE",
|
||||
"md_fn": "airplane_mode_toggle_get_app_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -96,
|
||||
"enum": "TIMELINE_PAST",
|
||||
"md_fn": "timeline_past_get_app_info",
|
||||
"target_platforms": [
|
||||
"snowy",
|
||||
"spalding",
|
||||
"silk",
|
||||
"robert"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": -97,
|
||||
"enum": "SPORTS",
|
||||
"md_fn": "sports_app_get_info"
|
||||
}
|
||||
],
|
||||
"resource_apps": [
|
||||
{
|
||||
"id": -52,
|
||||
"enum": "GOLF",
|
||||
"name": "Golf",
|
||||
"uuid": "cf1e816a-9db0-4511-bbb8-f60c48ca8fac",
|
||||
"bin_resource_id": "RESOURCE_ID_STORED_APP_GOLF",
|
||||
"icon_resource_id": "DEFAULT_MENU_ICON"
|
||||
}
|
||||
]
|
||||
}
|
||||
133
src/fw/shell/normal/system_app_state_machine.c
Normal file
133
src/fw/shell/normal/system_app_state_machine.c
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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/system_app_state_machine.h"
|
||||
|
||||
#include "apps/core_apps/panic_window_app.h"
|
||||
#include "apps/system_apps/battery_critical_app.h"
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "apps/system_apps/launcher/launcher_app.h"
|
||||
#include "apps/watch/low_power/low_power_face.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "kernel/panic.h"
|
||||
#include "resource/resource.h"
|
||||
#include "services/common/battery/battery_monitor.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/logging.h"
|
||||
#include "process_management/app_manager.h"
|
||||
|
||||
//! @file system_app_state_machine.c
|
||||
//!
|
||||
//! This file implements our app to app flow that makes up our normal shell. It defines
|
||||
//! which app first runs at start up and what app should be launched to replace the current
|
||||
//! app if the current app wants to close.
|
||||
//!
|
||||
//! The logic for which app should replace closing apps is a little tricky. Apps can be launched
|
||||
//! in various ways, either due to direct user interaction (selecting an app in the launcher) or
|
||||
//! through the phone app using pebble protocol (for example, a new app being installed or a
|
||||
//! companion app launching its watchapp in response to an event). What we want to happen is
|
||||
//! the user can then close that app and end up in a rough approximation of where they came from.
|
||||
//!
|
||||
//! The way we implement this is by having two apps that make up roots of the graph. If you're
|
||||
//! in the launcher and you launch an app, closing that app will return to the launcher. If you
|
||||
//! attempt to nest further (you launch an app from the launcher and that app in turn launches
|
||||
//! another app), closing any app will still return you to the launcher. This is done to prevent
|
||||
//! the stack from growing too deep and having to exit a ton of apps to get back to where you want.
|
||||
//! The watchface is also a root (closing an app that launched while you were in a watchface
|
||||
//! will return to you to the watchface). Finally, closing the launcher will return you to the
|
||||
//! watchface, and closing the watchface (either by pressing select or the watchface crashing)
|
||||
//! should take you to the launcher.
|
||||
//!
|
||||
//! Launching any watchface for any reason will put you in the "root watchface" state.
|
||||
//!
|
||||
//! Below is a pretty ASCII picture to describe the states we can be in. What happens when you
|
||||
//! close an app is illustrated with the arrow with the X.
|
||||
//!
|
||||
//! +---------------------+----+ +-------------------------+-----+
|
||||
//! | Remote Launched App | | | Remote Launched App | |
|
||||
//! +---------------+-----+ <--+ | Launcher Launched App | <---+
|
||||
//! X +---------------+---------+
|
||||
//! ^ | X
|
||||
//! | v ^ |
|
||||
//! | | v
|
||||
//! +----+----------------+ +X-----> +------+------------------+
|
||||
//! | Watchface | | Launcher |
|
||||
//! +---------------------+ <-----X+ +-------------------------+
|
||||
//!
|
||||
|
||||
//! As per the above block comment, are we currently rooted in the watchface stack or the
|
||||
//! launcher stack?
|
||||
static bool s_rooted_in_watchface = false;
|
||||
|
||||
const PebbleProcessMd* system_app_state_machine_system_start(void) {
|
||||
// start critical battery app when necessary
|
||||
if (battery_monitor_critical_lockout()) {
|
||||
return battery_critical_get_app_info();
|
||||
}
|
||||
|
||||
if (low_power_is_active()) {
|
||||
return low_power_face_get_app_info();
|
||||
}
|
||||
|
||||
if (launcher_panic_get_current_error() != 0) {
|
||||
return panic_app_get_app_info();
|
||||
}
|
||||
|
||||
return launcher_menu_app_get_app_info();
|
||||
}
|
||||
|
||||
//! @return True if the currently running app is an installed watchface
|
||||
static bool prv_current_app_is_watchface(void) {
|
||||
return app_install_is_watchface(app_manager_get_current_app_id());
|
||||
}
|
||||
|
||||
AppInstallId system_app_state_machine_get_last_registered_app(void) {
|
||||
// If we're rooted in the watchface but we're not the watchface itself, or the launcher
|
||||
// is closing, we should launch the watchface.
|
||||
if ((s_rooted_in_watchface && !prv_current_app_is_watchface())
|
||||
|| (app_manager_get_current_app_md() == launcher_menu_app_get_app_info())) {
|
||||
return watchface_get_default_install_id();
|
||||
}
|
||||
|
||||
return APP_ID_LAUNCHER_MENU;
|
||||
}
|
||||
|
||||
const PebbleProcessMd* system_app_state_machine_get_default_app(void) {
|
||||
return launcher_menu_app_get_app_info();
|
||||
}
|
||||
|
||||
void system_app_state_machine_register_app_launch(AppInstallId app_id) {
|
||||
if (app_id == APP_ID_LAUNCHER_MENU) {
|
||||
s_rooted_in_watchface = false;
|
||||
} else if (app_install_is_watchface(app_id)) {
|
||||
s_rooted_in_watchface = true;
|
||||
}
|
||||
|
||||
// Other app launches don't modify our root so just ignore them.
|
||||
}
|
||||
|
||||
void system_app_state_machine_panic(void) {
|
||||
if (app_manager_is_initialized()) {
|
||||
app_manager_launch_new_app(&(AppLaunchConfig) {
|
||||
.md = panic_app_get_app_info(),
|
||||
});
|
||||
}
|
||||
|
||||
// Else, just wait for the app_manager to initialize to show the panic app using
|
||||
// system_app_state_machine_system_start().
|
||||
}
|
||||
|
||||
202
src/fw/shell/normal/watchface.c
Normal file
202
src/fw/shell/normal/watchface.c
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* 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 "watchface.h"
|
||||
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "apps/system_apps/launcher/launcher_app.h"
|
||||
#include "apps/system_apps/settings/settings_quick_launch.h"
|
||||
#include "apps/system_apps/settings/settings_quick_launch_app_menu.h"
|
||||
#include "apps/system_apps/settings/settings_quick_launch_setup_menu.h"
|
||||
#include "apps/system_apps/timeline/timeline.h"
|
||||
#include "apps/watch/low_power/low_power_face.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "popups/timeline/peek.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/compositor/compositor_transitions.h"
|
||||
#include "services/normal/notifications/do_not_disturb.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define QUICK_LAUNCH_HOLD_MS (400)
|
||||
|
||||
static ClickManager s_click_manager;
|
||||
|
||||
static bool prv_should_ignore_button_click(void) {
|
||||
if (app_manager_get_task_context()->closing_state != ProcessRunState_Running) {
|
||||
// Ignore if the app is not running (such as if it is in the process of closing)
|
||||
return true;
|
||||
}
|
||||
if (low_power_is_active()) {
|
||||
// If we're in low power mode we dont allow any interaction
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void prv_launch_app_via_button(AppLaunchEventConfig *config,
|
||||
ClickRecognizerRef recognizer) {
|
||||
config->common.button = click_recognizer_get_button_id(recognizer);
|
||||
app_manager_put_launch_app_event(config);
|
||||
}
|
||||
|
||||
static void prv_quick_launch_handler(ClickRecognizerRef recognizer, void *data) {
|
||||
ButtonId button = click_recognizer_get_button_id(recognizer);
|
||||
if (!quick_launch_is_enabled(button)) {
|
||||
return;
|
||||
}
|
||||
AppInstallId app_id = quick_launch_get_app(button);
|
||||
if (app_id == INSTALL_ID_INVALID) {
|
||||
app_id = app_install_get_id_for_uuid(&quick_launch_setup_get_app_info()->uuid);
|
||||
}
|
||||
prv_launch_app_via_button(&(AppLaunchEventConfig) {
|
||||
.id = app_id,
|
||||
.common.reason = APP_LAUNCH_QUICK_LAUNCH,
|
||||
}, recognizer);
|
||||
}
|
||||
|
||||
static void prv_launch_timeline(ClickRecognizerRef recognizer, void *data) {
|
||||
static TimelineArgs s_timeline_args;
|
||||
const bool is_up = (click_recognizer_get_button_id(recognizer) == BUTTON_ID_UP);
|
||||
if (is_up) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Launching timeline in past mode.");
|
||||
s_timeline_args.direction = TimelineIterDirectionPast;
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_TIMELINE_PAST_LAUNCH_COUNT, AnalyticsClient_System);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Launching timeline in future mode.");
|
||||
s_timeline_args.direction = TimelineIterDirectionFuture;
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_TIMELINE_FUTURE_LAUNCH_COUNT, AnalyticsClient_System);
|
||||
}
|
||||
s_timeline_args.launch_into_pin = true;
|
||||
s_timeline_args.stay_in_list_view = true;
|
||||
timeline_peek_get_item_id(&s_timeline_args.pin_id);
|
||||
|
||||
const CompositorTransition *animation = NULL;
|
||||
const bool is_future = (s_timeline_args.direction == TimelineIterDirectionFuture);
|
||||
const bool timeline_is_destination = true;
|
||||
#if PBL_ROUND
|
||||
animation = compositor_dot_transition_timeline_get(is_future, timeline_is_destination);
|
||||
#else
|
||||
const bool jump = (!uuid_is_invalid(&s_timeline_args.pin_id) && !timeline_peek_is_first_event());
|
||||
animation = jump ? compositor_peek_transition_timeline_get() :
|
||||
compositor_slide_transition_timeline_get(is_future, timeline_is_destination,
|
||||
timeline_peek_is_future_empty());
|
||||
#endif
|
||||
prv_launch_app_via_button(&(AppLaunchEventConfig) {
|
||||
.id = APP_ID_TIMELINE,
|
||||
.common.args = &s_timeline_args,
|
||||
.common.transition = animation,
|
||||
}, recognizer);
|
||||
}
|
||||
|
||||
static void prv_configure_click_handler(ButtonId button_id, ClickHandler single_click_handler) {
|
||||
ClickConfig *cfg = &s_click_manager.recognizers[button_id].config;
|
||||
cfg->long_click.delay_ms = QUICK_LAUNCH_HOLD_MS;
|
||||
cfg->long_click.handler = prv_quick_launch_handler;
|
||||
cfg->click.handler = single_click_handler;
|
||||
}
|
||||
|
||||
static void prv_launch_launcher_app(ClickRecognizerRef recognizer, void *data) {
|
||||
static const LauncherMenuArgs s_launcher_args = { .reset_scroll = true };
|
||||
prv_launch_app_via_button(&(AppLaunchEventConfig) {
|
||||
.id = APP_ID_LAUNCHER_MENU,
|
||||
.common.args = &s_launcher_args,
|
||||
}, recognizer);
|
||||
}
|
||||
|
||||
#if CAPABILITY_HAS_CORE_NAVIGATION4
|
||||
static void prv_launch_health_app(ClickRecognizerRef recognizer, void *data) {
|
||||
prv_launch_app_via_button(&(AppLaunchEventConfig) {
|
||||
.id = APP_ID_HEALTH_APP,
|
||||
}, recognizer);
|
||||
}
|
||||
#endif // CAPABILITY_HAS_CORE_NAVIGATION4
|
||||
|
||||
static ClickHandler prv_get_up_click_handler(void) {
|
||||
#if CAPABILITY_HAS_CORE_NAVIGATION4
|
||||
return prv_launch_health_app;
|
||||
#else
|
||||
return prv_launch_timeline;
|
||||
#endif // CAPABILITY_HAS_CORE_NAVIGATION4
|
||||
}
|
||||
|
||||
static void prv_dismiss_timeline_peek(ClickRecognizerRef recognizer, void *data) {
|
||||
timeline_peek_dismiss();
|
||||
}
|
||||
|
||||
static void prv_watchface_configure_click_handlers(void) {
|
||||
prv_configure_click_handler(BUTTON_ID_UP, prv_get_up_click_handler());
|
||||
prv_configure_click_handler(BUTTON_ID_DOWN, prv_launch_timeline);
|
||||
prv_configure_click_handler(BUTTON_ID_SELECT, prv_launch_launcher_app);
|
||||
prv_configure_click_handler(BUTTON_ID_BACK, prv_dismiss_timeline_peek);
|
||||
}
|
||||
|
||||
void watchface_init(void) {
|
||||
click_manager_init(&s_click_manager);
|
||||
prv_watchface_configure_click_handlers();
|
||||
}
|
||||
|
||||
void watchface_handle_button_event(PebbleEvent *e) {
|
||||
if (prv_should_ignore_button_click()) {
|
||||
return;
|
||||
}
|
||||
switch (e->type) {
|
||||
case PEBBLE_BUTTON_DOWN_EVENT:
|
||||
click_recognizer_handle_button_down(&s_click_manager.recognizers[e->button.button_id]);
|
||||
break;
|
||||
case PEBBLE_BUTTON_UP_EVENT:
|
||||
click_recognizer_handle_button_up(&s_click_manager.recognizers[e->button.button_id]);
|
||||
break;
|
||||
default:
|
||||
PBL_CROAK("Invalid event type: %u", e->type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_watchface_launch_low_power(void) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Switching default watchface to low_power_mode watchface");
|
||||
app_manager_put_launch_app_event(&(AppLaunchEventConfig) {
|
||||
.id = APP_ID_LOW_POWER_FACE,
|
||||
});
|
||||
}
|
||||
|
||||
void watchface_launch_default(const CompositorTransition *animation) {
|
||||
app_manager_put_launch_app_event(&(AppLaunchEventConfig) {
|
||||
.id = watchface_get_default_install_id(),
|
||||
.common.transition = animation,
|
||||
});
|
||||
}
|
||||
|
||||
static void kernel_callback_watchface_launch(void* data) {
|
||||
watchface_launch_default(NULL);
|
||||
}
|
||||
|
||||
void command_watch(void) {
|
||||
launcher_task_add_callback(kernel_callback_watchface_launch, NULL);
|
||||
}
|
||||
|
||||
void watchface_start_low_power(void) {
|
||||
app_manager_set_minimum_run_level(ProcessAppRunLevelNormal);
|
||||
prv_watchface_launch_low_power();
|
||||
}
|
||||
|
||||
void watchface_reset_click_manager(void) {
|
||||
click_manager_reset(&s_click_manager);
|
||||
}
|
||||
35
src/fw/shell/normal/watchface.h
Normal file
35
src/fw/shell/normal/watchface.h
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/events.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
|
||||
void watchface_init(void);
|
||||
|
||||
void watchface_handle_button_event(PebbleEvent *e);
|
||||
|
||||
void watchface_set_default_install_id(AppInstallId id);
|
||||
|
||||
AppInstallId watchface_get_default_install_id(void);
|
||||
|
||||
void watchface_launch_default(const CompositorTransition *animation);
|
||||
|
||||
void watchface_start_low_power(void);
|
||||
|
||||
void watchface_reset_click_manager(void);
|
||||
89
src/fw/shell/normal/welcome.c
Normal file
89
src/fw/shell/normal/welcome.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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 "welcome.h"
|
||||
|
||||
#include "kernel/event_loop.h"
|
||||
#include "resource/timeline_resource_ids.auto.h"
|
||||
#include "services/common/evented_timer.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/common/shared_prf_storage/shared_prf_storage.h"
|
||||
#include "shell/prefs.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/size.h"
|
||||
|
||||
static void prv_push_welcome_notification(void *UNUSED data) {
|
||||
AttributeList notif_attr_list = {};
|
||||
attribute_list_add_uint32(¬if_attr_list, AttributeIdIconTiny,
|
||||
TIMELINE_RESOURCE_NOTIFICATION_FLAG);
|
||||
attribute_list_add_cstring(¬if_attr_list, AttributeIdTitle,
|
||||
/// Welcome title text welcoming a 3.x user to 4.x
|
||||
i18n_get("Pebble Updated!", ¬if_attr_list));
|
||||
/// Welcome body text welcoming a 3.x user to 4.x.
|
||||
const char *welcome_text = i18n_get(
|
||||
"For activity and sleep tracking, press up from your watch face.\n\n"
|
||||
"Press down for current and future events.\n\n"
|
||||
"Read more at blog.pebble.com",
|
||||
¬if_attr_list);
|
||||
attribute_list_add_cstring(¬if_attr_list, AttributeIdBody, welcome_text);
|
||||
attribute_list_add_uint8(¬if_attr_list, AttributeIdBgColor, GColorOrangeARGB8);
|
||||
|
||||
AttributeList dismiss_action_attr_list = {};
|
||||
attribute_list_add_cstring(&dismiss_action_attr_list, AttributeIdTitle,
|
||||
i18n_get("Dismiss", ¬if_attr_list));
|
||||
|
||||
int action_id = 0;
|
||||
TimelineItemAction actions[] = {
|
||||
{
|
||||
.id = action_id++,
|
||||
.type = TimelineItemActionTypeDismiss,
|
||||
.attr_list = dismiss_action_attr_list,
|
||||
},
|
||||
};
|
||||
TimelineItemActionGroup action_group = {
|
||||
.num_actions = ARRAY_LENGTH(actions),
|
||||
.actions = actions,
|
||||
};
|
||||
|
||||
const time_t now = rtc_get_time();
|
||||
TimelineItem *item = timeline_item_create_with_attributes(
|
||||
now, 0, TimelineItemTypeNotification, LayoutIdNotification, ¬if_attr_list, &action_group);
|
||||
i18n_free_all(¬if_attr_list);
|
||||
attribute_list_destroy_list(¬if_attr_list);
|
||||
attribute_list_destroy_list(&dismiss_action_attr_list);
|
||||
|
||||
if (!item) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Failed to welcome the user.");
|
||||
return;
|
||||
}
|
||||
|
||||
item->header.from_watch = true;
|
||||
notifications_add_notification(item);
|
||||
timeline_item_destroy(item);
|
||||
welcome_set_welcome_version(WelcomeVersionCurrent);
|
||||
}
|
||||
|
||||
void welcome_push_notification(bool factory_reset_or_first_use) {
|
||||
const WelcomeVersion version = welcome_get_welcome_version();
|
||||
// This check only works if it is called before getting started complete is set
|
||||
if (!factory_reset_or_first_use && (version < WelcomeVersion_4xNormalFirmware)) {
|
||||
// This has completed getting started on a previous normal firmware, welcome them if the
|
||||
// version is before 4.x
|
||||
// We wait some time since notification storage takes time to initialize
|
||||
launcher_task_add_callback(prv_push_welcome_notification, NULL);
|
||||
}
|
||||
}
|
||||
44
src/fw/shell/normal/welcome.h
Normal file
44
src/fw/shell/normal/welcome.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//! Version of the welcoming of the user to the normal firmware
|
||||
typedef enum WelcomeVersion {
|
||||
//! Initial version or never launched normal firmware
|
||||
WelcomeVersion_InitialVersion = 0,
|
||||
//! 4.x Normal Firmware
|
||||
WelcomeVersion_4xNormalFirmware = 1,
|
||||
|
||||
WelcomeVersionCount,
|
||||
//! WelcomeVersion is an increasing version number. WelcomeVersionCurrent must
|
||||
//! not decrement. This should ensure that the current version is always the latest.
|
||||
WelcomeVersionCurrent = WelcomeVersionCount - 1,
|
||||
} WelcomeVersion;
|
||||
|
||||
//! Welcomes the user to a newer normal firmware they have not used yet if they have used an older
|
||||
//! normal firmware and the newer normal firmware warrants a notification.
|
||||
//! @note This must be called before getting started completed is set in shared prf storage.
|
||||
void welcome_push_notification(bool factory_reset_or_first_use);
|
||||
|
||||
//! Set the welcome version. This is persisted in shell prefs.
|
||||
void welcome_set_welcome_version(uint8_t version);
|
||||
|
||||
//! Get the welcome version
|
||||
uint8_t welcome_get_welcome_version(void);
|
||||
Reference in New Issue
Block a user