/* * 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 #include #include #include #include #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 #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(); }