mirror of
https://github.com/google/pebble.git
synced 2025-05-28 22:13:13 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
507
src/fw/kernel/event_loop.c
Normal file
507
src/fw/kernel/event_loop.c
Normal file
|
@ -0,0 +1,507 @@
|
|||
/*
|
||||
* 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 "event_loop.h"
|
||||
#include "events.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "applib/app_launch_reason.h"
|
||||
#include "applib/battery_state_service.h"
|
||||
#include "applib/connection_service.h"
|
||||
#include "applib/graphics/graphics.h"
|
||||
#include "applib/graphics/text.h"
|
||||
#include "applib/tick_timer_service.h"
|
||||
#include "applib/ui/animation_private.h"
|
||||
#include "applib/ui/app_window_click_glue.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "applib/ui/window.h"
|
||||
#include "applib/ui/window_private.h"
|
||||
#include "comm/ble/kernel_le_client/kernel_le_client.h"
|
||||
#include "console/serial_console.h"
|
||||
#include "console/prompt.h"
|
||||
#include "drivers/backlight.h"
|
||||
#include "drivers/battery.h"
|
||||
#include "drivers/button.h"
|
||||
#include "drivers/task_watchdog.h"
|
||||
#include "kernel/kernel_applib_state.h"
|
||||
#include "kernel/low_power.h"
|
||||
#include "kernel/panic.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/ui/kernel_ui.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "kernel/util/factory_reset.h"
|
||||
#include "mcu/fpu.h"
|
||||
#include "pebble_errors.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/app_run_state.h"
|
||||
#include "process_management/process_manager.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/battery/battery_state.h"
|
||||
#include "services/common/battery/battery_monitor.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
#include "services/common/cron.h"
|
||||
#include "services/common/debounced_connection_service.h"
|
||||
#include "services/common/ecompass.h"
|
||||
#include "services/common/event_service.h"
|
||||
#include "services/common/evented_timer.h"
|
||||
#include "services/common/firmware_update.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/common/light.h"
|
||||
#include "services/common/new_timer/new_timer.h"
|
||||
#include "services/common/put_bytes/put_bytes.h"
|
||||
#include "services/common/status_led.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "services/common/vibe_pattern.h"
|
||||
#include "services/normal/accessory/accessory_manager.h"
|
||||
#include "services/normal/alarms/alarm.h"
|
||||
#include "services/normal/app_fetch_endpoint.h"
|
||||
#include "services/normal/blob_db/api.h"
|
||||
#include "services/normal/notifications/do_not_disturb.h"
|
||||
#include "services/normal/stationary.h"
|
||||
#include "services/normal/timeline/reminders.h"
|
||||
#include "services/normal/wakeup.h"
|
||||
#include "services/runlevel.h"
|
||||
#include "shell/normal/app_idle_timeout.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "shell/shell_event_loop.h"
|
||||
#include "shell/system_app_state_machine.h"
|
||||
#include "system/bootbits.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/reset.h"
|
||||
#include "system/testinfra.h"
|
||||
#include "util/bitset.h"
|
||||
#include "util/struct.h"
|
||||
#include "system/version.h"
|
||||
|
||||
#include <bluetooth/reconnect.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
static const uint32_t FORCE_QUIT_HOLD_MS = 1500;
|
||||
static int s_back_hold_timer = TIMER_INVALID_ID;
|
||||
|
||||
void launcher_task_add_callback(void (*callback)(void *data), void *data) {
|
||||
PebbleEvent event = {
|
||||
.type = PEBBLE_CALLBACK_EVENT,
|
||||
.callback = {
|
||||
.callback = callback,
|
||||
.data = data,
|
||||
},
|
||||
};
|
||||
event_put(&event);
|
||||
}
|
||||
|
||||
bool launcher_task_is_current_task(void) {
|
||||
return (pebble_task_get_current() == PebbleTask_KernelMain);
|
||||
}
|
||||
|
||||
//! Return true if event could cause pop-up
|
||||
//! Used in getting started and during firmware update
|
||||
static bool launcher_is_popup_event(PebbleEvent* e) {
|
||||
switch (e->type) {
|
||||
case PEBBLE_SYS_NOTIFICATION_EVENT:
|
||||
case PEBBLE_ALARM_CLOCK_EVENT:
|
||||
case PEBBLE_BATTERY_CONNECTION_EVENT:
|
||||
case PEBBLE_BATTERY_STATE_CHANGE_EVENT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static int s_block_popup_count = 0;
|
||||
|
||||
void launcher_block_popups(bool block) {
|
||||
if (block) {
|
||||
s_block_popup_count++;
|
||||
} else {
|
||||
PBL_ASSERTN(s_block_popup_count > 0);
|
||||
s_block_popup_count--;
|
||||
}
|
||||
}
|
||||
|
||||
bool launcher_popups_are_blocked(void) {
|
||||
return s_block_popup_count > 0;
|
||||
}
|
||||
|
||||
void launcher_cancel_force_quit(void) {
|
||||
new_timer_stop(s_back_hold_timer);
|
||||
}
|
||||
|
||||
static void launcher_force_quit_app(void *data) {
|
||||
if (low_power_is_active() || factory_reset_ongoing()) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Forcekill disabled due to low-power or factory-reset");
|
||||
return;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Force killing app.");
|
||||
app_manager_force_quit_to_launcher();
|
||||
}
|
||||
|
||||
static void back_button_force_quit_handler(void *data) {
|
||||
launcher_task_add_callback(launcher_force_quit_app, NULL);
|
||||
}
|
||||
|
||||
static void launcher_handle_button_event(PebbleEvent* e) {
|
||||
ButtonId button_id = e->button.button_id;
|
||||
const bool watchface_running = app_manager_is_watchface_running();
|
||||
|
||||
// trigger the backlight on any button down event
|
||||
if (e->type == PEBBLE_BUTTON_DOWN_EVENT) {
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_BUTTON_PRESSED_COUNT, AnalyticsClient_System);
|
||||
|
||||
if (button_id == BUTTON_ID_BACK && !watchface_running &&
|
||||
process_metadata_get_run_level(
|
||||
app_manager_get_current_app_md()) == ProcessAppRunLevelNormal) {
|
||||
// Start timer for force-quitting app
|
||||
bool success = new_timer_start(s_back_hold_timer, FORCE_QUIT_HOLD_MS, back_button_force_quit_handler, NULL,
|
||||
0 /*flags*/);
|
||||
PBL_ASSERTN(success);
|
||||
}
|
||||
light_button_pressed();
|
||||
} else if (e->type == PEBBLE_BUTTON_UP_EVENT) {
|
||||
if (button_id == BUTTON_ID_BACK) {
|
||||
launcher_cancel_force_quit();
|
||||
}
|
||||
light_button_released();
|
||||
}
|
||||
|
||||
app_idle_timeout_refresh();
|
||||
|
||||
if (compositor_is_animating()) {
|
||||
// mask the app task if we're already animating
|
||||
e->task_mask |= 1 << PebbleTask_App;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool is_modal_focused = (modal_manager_get_enabled() &&
|
||||
!(modal_manager_get_properties() & ModalProperty_Unfocused));
|
||||
if (is_modal_focused) {
|
||||
// mask the app task if a modal is on top
|
||||
e->task_mask |= 1 << PebbleTask_App;
|
||||
modal_manager_handle_button_event(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (watchface_running) {
|
||||
watchface_handle_button_event(e);
|
||||
// suppress the button event from the app task
|
||||
e->task_mask |= 1 << PebbleTask_App;
|
||||
}
|
||||
}
|
||||
|
||||
// This function should handle very basic events (Button clicks, app launching, battery events,
|
||||
// crashes, etc.
|
||||
static NOINLINE void prv_minimal_event_handler(PebbleEvent* e) {
|
||||
switch (e->type) {
|
||||
case PEBBLE_BUTTON_DOWN_EVENT:
|
||||
case PEBBLE_BUTTON_UP_EVENT:
|
||||
launcher_handle_button_event(e);
|
||||
return;
|
||||
|
||||
case PEBBLE_BATTERY_CONNECTION_EVENT: {
|
||||
const bool is_connected = e->battery_connection.is_connected;
|
||||
battery_state_handle_connection_event(is_connected);
|
||||
if (is_connected) {
|
||||
light_enable_interaction();
|
||||
} else {
|
||||
// Chances are the Pebble of our dear customer has been charging away
|
||||
// from the phone and is disconnected because of that. Try reconnecting
|
||||
// immediately upon disconnecting the charger:
|
||||
bt_driver_reconnect_reset_interval();
|
||||
bt_driver_reconnect_try_now(false /*ignore_paused*/);
|
||||
}
|
||||
#if STATIONARY_MODE
|
||||
stationary_handle_battery_connection_change_event();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
case PEBBLE_BATTERY_STATE_CHANGE_EVENT:
|
||||
battery_monitor_handle_state_change_event(e->battery_state.new_state);
|
||||
#if CAPABILITY_HAS_MAGNETOMETER
|
||||
ecompass_handle_battery_state_change_event(e->battery_state.new_state);
|
||||
#endif
|
||||
return;
|
||||
|
||||
case PEBBLE_RENDER_READY_EVENT:
|
||||
compositor_app_render_ready();
|
||||
return;
|
||||
|
||||
case PEBBLE_ACCEL_SHAKE_EVENT:
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_ACCEL_SHAKE_COUNT, AnalyticsClient_System);
|
||||
if (backlight_is_motion_enabled()) {
|
||||
light_enable_interaction();
|
||||
}
|
||||
return;
|
||||
|
||||
case PEBBLE_PANIC_EVENT:
|
||||
launcher_panic(e->panic.error_code);
|
||||
break;
|
||||
|
||||
case PEBBLE_APP_LAUNCH_EVENT:
|
||||
if (!app_install_is_app_running(e->launch_app.id)) {
|
||||
process_manager_launch_process(&(ProcessLaunchConfig) {
|
||||
.id = e->launch_app.id,
|
||||
.common = NULL_SAFE_FIELD_ACCESS(e->launch_app.data, common, (LaunchConfigCommon) {}),
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
case PEBBLE_WORKER_LAUNCH_EVENT:
|
||||
if (!app_install_is_worker_running(e->launch_app.id)) {
|
||||
process_manager_launch_process(&(ProcessLaunchConfig) {
|
||||
.id = e->launch_app.id,
|
||||
.common = NULL_SAFE_FIELD_ACCESS(e->launch_app.data, common, (LaunchConfigCommon) {}),
|
||||
.worker = true,
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
case PEBBLE_CALLBACK_EVENT:
|
||||
e->callback.callback(e->callback.data);
|
||||
return;
|
||||
|
||||
case PEBBLE_PROCESS_KILL_EVENT:
|
||||
process_manager_close_process(e->kill.task, e->kill.gracefully);
|
||||
return;
|
||||
|
||||
case PEBBLE_SUBSCRIPTION_EVENT:
|
||||
// App button events depend on this, so this needs to be in the minimal event handler.
|
||||
event_service_handle_subscription(&e->subscription);
|
||||
return;
|
||||
|
||||
default:
|
||||
PBL_LOG_VERBOSE("Received an unhandled event (%u)", e->type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static NOINLINE void prv_handle_app_fetch_request_event(PebbleEvent *e) {
|
||||
AppInstallEntry entry;
|
||||
PBL_ASSERTN(app_install_get_entry_for_install_id(e->app_fetch_request.id, &entry));
|
||||
bool has_worker = app_install_entry_has_worker(&entry);
|
||||
app_fetch_binaries(&entry.uuid, e->app_fetch_request.id, has_worker);
|
||||
}
|
||||
|
||||
static NOINLINE void prv_extended_event_handler(PebbleEvent* e) {
|
||||
switch (e->type) {
|
||||
case PEBBLE_APP_OUTBOX_MSG_EVENT:
|
||||
e->app_outbox_msg.callback(e->app_outbox_msg.data);
|
||||
return;
|
||||
|
||||
case PEBBLE_APP_FETCH_REQUEST_EVENT:
|
||||
prv_handle_app_fetch_request_event(e);
|
||||
return;
|
||||
|
||||
case PEBBLE_PUT_BYTES_EVENT:
|
||||
// TODO: inform the other things interested in put_bytes (apps?)
|
||||
firmware_update_pb_event_handler(&e->put_bytes);
|
||||
#ifndef RECOVERY_FW
|
||||
app_fetch_put_bytes_event_handler(&e->put_bytes);
|
||||
#endif
|
||||
return;
|
||||
|
||||
case PEBBLE_SYSTEM_MESSAGE_EVENT:
|
||||
firmware_update_event_handler(&e->firmware_update);
|
||||
return;
|
||||
|
||||
case PEBBLE_ECOMPASS_SERVICE_EVENT:
|
||||
#if CAPABILITY_HAS_MAGNETOMETER
|
||||
ecompass_service_handle();
|
||||
#endif
|
||||
return;
|
||||
|
||||
case PEBBLE_SET_TIME_EVENT:
|
||||
{
|
||||
#ifndef RECOVERY_FW
|
||||
PebbleSetTimeEvent *set_time_info = &e->set_time_info;
|
||||
|
||||
// The phone and watch time may be out of sync by a second or two (since
|
||||
// we don't account for the time it takes for the request to change the
|
||||
// time to propagate to the watch). Thus only update our alarm time if
|
||||
// the timezone has changed or a 'substantial' time has passed, or DST
|
||||
// state has changed.
|
||||
if (set_time_info->gmt_offset_delta != 0 ||
|
||||
set_time_info->dst_changed ||
|
||||
ABS(set_time_info->utc_time_delta) > 15) {
|
||||
alarm_handle_clock_change();
|
||||
wakeup_handle_clock_change();
|
||||
cron_service_handle_clock_change(set_time_info);
|
||||
}
|
||||
|
||||
// TODO: evaluate if these need to change on every time update
|
||||
do_not_disturb_handle_clock_change();
|
||||
reminders_update_timer();
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
case PEBBLE_BLE_SCAN_EVENT:
|
||||
case PEBBLE_BLE_CONNECTION_EVENT:
|
||||
case PEBBLE_BLE_GATT_CLIENT_EVENT:
|
||||
kernel_le_client_handle_event(e);
|
||||
return;
|
||||
|
||||
case PEBBLE_COMM_SESSION_EVENT: {
|
||||
PebbleCommSessionEvent *comm_session_event = &e->bluetooth.comm_session_event;
|
||||
debounced_connection_service_handle_event(comm_session_event);
|
||||
put_bytes_handle_comm_session_event(comm_session_event);
|
||||
#ifndef RECOVERY_FW
|
||||
if (comm_session_event->is_system) {
|
||||
// tell the phone which app is running
|
||||
const Uuid *running_uuid = &app_manager_get_current_app_md()->uuid;
|
||||
if (running_uuid != NULL) {
|
||||
app_run_state_send_update(running_uuid, RUNNING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//! Tasks that have to be done in between each event.
|
||||
static void event_loop_upkeep(void) {
|
||||
modal_manager_event_loop_upkeep();
|
||||
}
|
||||
|
||||
// NOTE: Marking this as NOINLINE saves us 150+ bytes on the KernelMain stack
|
||||
static void NOINLINE prv_handle_event(PebbleEvent *e) {
|
||||
prv_minimal_event_handler(e);
|
||||
|
||||
// FIXME: This logic is pretty wacky, but I'm going to leave it as is to refactor later out of
|
||||
// fear of breaking something. This should mimic the exact same behaviour as before but
|
||||
// flattened.
|
||||
if (s_block_popup_count > 0) {
|
||||
// A service has requested that the launcher block any events that may cause
|
||||
// pop-ups
|
||||
if (launcher_is_popup_event(e)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!launcher_panic_get_current_error()) {
|
||||
prv_extended_event_handler(e);
|
||||
}
|
||||
|
||||
shell_event_loop_handle_event(e);
|
||||
}
|
||||
|
||||
static NOINLINE void prv_launcher_main_loop_init(void) {
|
||||
s_back_hold_timer = new_timer_create();
|
||||
|
||||
process_manager_init();
|
||||
app_manager_init();
|
||||
worker_manager_init();
|
||||
vibes_init();
|
||||
battery_monitor_init();
|
||||
evented_timer_init();
|
||||
#if CAPABILITY_HAS_MAGNETOMETER
|
||||
ecompass_service_init();
|
||||
#endif
|
||||
tick_timer_service_init();
|
||||
debounced_connection_service_init();
|
||||
event_service_system_init();
|
||||
#if CAPABILITY_HAS_ACCESSORY_CONNECTOR
|
||||
accessory_manager_init();
|
||||
#endif
|
||||
|
||||
modal_manager_init();
|
||||
|
||||
shell_event_loop_init();
|
||||
|
||||
#if STATIONARY_MODE
|
||||
stationary_init();
|
||||
#endif
|
||||
|
||||
task_watchdog_bit_set(PebbleTask_KernelMain);
|
||||
|
||||
// if we are in launcher panic, don't turn on any extra services.
|
||||
const RunLevel run_level = launcher_panic_get_current_error() ? RunLevel_BareMinimum
|
||||
: RunLevel_Normal;
|
||||
services_set_runlevel(run_level);
|
||||
|
||||
// emulate a button press-and-release to turn on/off the backlight
|
||||
light_button_pressed();
|
||||
light_button_released();
|
||||
|
||||
#ifndef RECOVERY_FW
|
||||
i18n_set_resource(RESOURCE_ID_STRINGS);
|
||||
#endif
|
||||
app_manager_start_first_app();
|
||||
|
||||
#ifndef RECOVERY_FW
|
||||
// Launch the default worker. If any of the buttons are down, or we hit 2 strikes already,
|
||||
// skip this. This insures that we don't enter PRF for a bad worker.
|
||||
if (launcher_panic_get_current_error()) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not launching worker because launcher panic");
|
||||
} else if (button_get_state_bits() != 0) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not launching worker because button held");
|
||||
} else if (boot_bit_test(BOOT_BIT_FW_START_FAIL_STRIKE_TWO)) {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not launching worker because of 2 strikes");
|
||||
} else {
|
||||
process_manager_launch_process(&(ProcessLaunchConfig) {
|
||||
.id = worker_manager_get_default_install_id(),
|
||||
.worker = true,
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
notify_system_ready_for_communication();
|
||||
serial_console_enable_prompt();
|
||||
}
|
||||
|
||||
void launcher_main_loop(void) {
|
||||
PBL_LOG(LOG_LEVEL_ALWAYS, "Starting Launcher");
|
||||
|
||||
prv_launcher_main_loop_init();
|
||||
|
||||
while (1) {
|
||||
task_watchdog_bit_set(PebbleTask_KernelMain);
|
||||
|
||||
// We make this PebbleEvent static to save stack space
|
||||
static PebbleEvent e;
|
||||
if (event_take_timeout(&e, 1000)) {
|
||||
const PebbleTaskBitset kernel_main_task_bit = (1 << PebbleTask_KernelMain);
|
||||
const bool is_not_masked_out_from_kernel_main = !(e.task_mask & kernel_main_task_bit);
|
||||
if (is_not_masked_out_from_kernel_main) {
|
||||
prv_handle_event(&e);
|
||||
}
|
||||
|
||||
event_service_handle_event(&e);
|
||||
|
||||
event_cleanup(&e);
|
||||
|
||||
mcu_fpu_cleanup();
|
||||
event_loop_upkeep();
|
||||
}
|
||||
}
|
||||
|
||||
__builtin_unreachable();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue