mirror of
https://github.com/google/pebble.git
synced 2025-05-07 10:21:40 -04:00
507 lines
16 KiB
C
507 lines
16 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "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();
|
|
}
|