/* * 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 "clar.h" #include "process_management/app_manager.h" #include "applib/app_comm.h" #include "applib/graphics/framebuffer.h" #include "applib/rockyjs/rocky_res.h" #include "applib/ui/window_stack.h" #include "applib/ui/window_stack_private.h" #include "drivers/mpu.h" #include "kernel/util/segment.h" #include "popups/crashed_ui.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 "resource/resource_ids.auto.h" #include "util/heap.h" // Fakes #include "fake_new_timer.h" #include "fake_pbl_malloc.h" #include "fake_rtc.h" // Stubs #include "stubs_accel_service.h" #include "stubs_analytics.h" #include "stubs_analytics_external.h" #include "stubs_animation_service.h" #include "stubs_app_state.h" #include "stubs_applib_resource.h" #include "stubs_cache.h" #include "stubs_compositor.h" #include "stubs_dialog.h" #include "stubs_events.h" #include "stubs_expandable_dialog.h" #include "stubs_gettext.h" #include "stubs_i18n.h" #include "stubs_logging.h" #include "stubs_modal_manager.h" #include "stubs_mpu.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_persist.h" #include "stubs_print.h" #include "stubs_prompt.h" #include "stubs_rand_ptr.h" #include "stubs_resources.h" #include "stubs_serial.h" #include "stubs_simple_dialog.h" #include "stubs_syscall_internal.h" #include "stubs_task.h" #include "stubs_tick.h" #include "stubs_timeline_peek.h" #include "stubs_worker_manager.h" // Fake "Apps" /////////////////////////////////////////////////////////// static PebbleProcessMdSystem s_launch_app = { .name = "Launch App", .common = { // UUID: 7bbff9bc-b762-4219-9003-4086675d625d .uuid = {0x7b, 0xbf, 0xf9, 0xbc, 0xb7, 0x62, 0x42, 0x19, 0x90, 0x03, 0x40, 0x86, 0x67, 0x5d, 0x62, 0x5d}, }, }; static PebbleProcessMdSystem s_root_app = { .name = "Root App", .common = { // UUID: 3fca66e2-8c66-46c6-8011-330fccc9baa9 .uuid = {0x3f, 0xca, 0x66, 0xe2, 0x8c, 0x66, 0x46, 0xc6, 0x80, 0x11, 0x33, 0x0f, 0xcc, 0xc9, 0xba, 0xa9}, }, }; static PebbleProcessMdSystem s_third_party_app = { .name = "Third Party App", .common = { .is_unprivileged = true, // UUID: 04c52401-4dbe-408b-b73a-0e80ef09af74 .uuid = {0x04, 0xc5, 0x24, 0x01, 0x4d, 0xbe, 0x40, 0x8b, 0xb7, 0x3a, 0x0e, 0x80, 0xef, 0x09, 0xaf, 0x74}, }, }; static PebbleProcessMdFlash s_borked_app = { .name = "Borked Mc'Rib", .common = { .is_unprivileged = true, .process_storage = ProcessStorageFlash, // UUID: 25a9e7ff-de9e-4dda-b745-afdd75aaa53b .uuid = {0x25, 0xa9, 0xe7, 0xff, 0xde, 0x9e, 0x4d, 0xda, 0xb7, 0x45, 0xaf, 0xdd, 0x75, 0xaa, 0xa5, 0x3b}, }, .sdk_version = {.major = PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR, .minor = PROCESS_INFO_CURRENT_SDK_VERSION_MINOR }, }; static PebbleEvent s_last_to_app_event; static uint32_t s_app_task_control_reg; // Fakes /////////////////////////////////////////////////////////// // Just fake this to something we can use in the fake functions below #define APP_ID_DEFAULT_WATCHFACE ((AppInstallId)-1337) PebbleTask pebble_task_get_current(void) { return PebbleTask_App; } AppInstallId watchface_get_default_install_id(void) { return APP_ID_DEFAULT_WATCHFACE; } const PebbleProcessMd* launcher_menu_app_get_app_info(void) { return (PebbleProcessMd*)&s_root_app; } const PebbleProcessMd *app_install_get_md(AppInstallId id, bool worker) { if (id == APP_ID_DEFAULT_WATCHFACE) { static const PebbleProcessMdSystem s_default_watchface_md = { .common.process_type = ProcessTypeWatchface, }; return (const PebbleProcessMd *)&s_default_watchface_md; } else { return launcher_menu_app_get_app_info(); } } void app_install_release_md(const PebbleProcessMd *md) { } // Stubs /////////////////////////////////////////////////////////// char __APP_RAM__[1024*128]; char __APP_RAM_end__; char __WORKER_RAM__[1024*12]; char __WORKER_RAM_end__; MemorySegment prv_get_app_ram_segment(void) { return (MemorySegment) { __APP_RAM__, &__APP_RAM__[1024*128] }; } size_t prv_get_stack_guard_size(void) { return 32; } void _REENT_INIT_PTR(void) { } void app_comm_set_sniff_interval(const SniffInterval interval) { } void app_idle_timeout_start(void) { } void app_idle_timeout_stop(void) { } void app_inbox_service_unregister_all(void) { } void app_outbox_service_cleanup_all_pending_messages(void) { } AppInstallId app_install_get_id_for_uuid(const Uuid *uuid) { return 1; } void app_install_register_callback(struct AppInstallCallbackNode *callback_info) { } void app_install_notify_app_closed(void) { } void app_install_cleanup_registered_app_callbacks(void) { } bool app_install_get_entry_for_install_id(AppInstallId id, AppInstallEntry *entry) { return true; } bool app_install_entry_is_watchface(const AppInstallEntry *entry) { return true; } bool app_install_entry_is_hidden(const AppInstallEntry *entry) { return false; } bool app_install_entry_is_SDK_compatible(const AppInstallEntry *entry) { return true; } bool app_install_id_from_app_db(AppInstallId id) { return false; } bool app_cache_entry_exists(AppInstallId app_id) { return true; } const PebbleProcessMd* app_fetch_ui_get_app_info(void) { return NULL; } void app_message_close(void) { } void ble_app_cleanup(void) { } void dls_inactivate_sessions(PebbleTask task) { } void event_service_clear_process_subscriptions(void) { } void evented_timer_clear_process_timers(PebbleTask task) { } void launcher_task_add_callback(void (*callback)(void *data), void *data) { callback(data); } void app_run_state_send_update(const Uuid* uuid, AppState app_state) { return; } const PebbleProcessMd* system_app_state_machine_get_default_app(void) { return launcher_menu_app_get_app_info(); } void launcher_cancel_force_quit(void) { } void light_reset_user_controlled(void) { } void mpu_set_task_configurable_regions(MemoryRegion_t *task_params, const MpuRegion **region_ptrs) { } void task_init(void) {} void pebble_task_register(PebbleTask task, TaskHandle_t task_handle) { } void pebble_task_unregister(PebbleTask task) { } const char* pebble_task_get_name(PebbleTask task) { return "?"; } void pebble_task_create(PebbleTask pebble_task, TaskParameters_t *task_params, TaskHandle_t *handle) { } void * process_loader_load(const PebbleProcessMd *app_md, PebbleTask task, MemorySegment *segment) { if (app_md == (PebbleProcessMd *)&s_borked_app) { return NULL; } else { return __APP_RAM__; } } void quick_launch_handle_analytics(void) { } void reboot_set_slot_of_last_launched_app(uint32_t app_slot) { } void sys_exit(int status) { } RockyResourceValidation rocky_app_validate_resources(const PebbleProcessMd *md) { return RockyResourceValidation_NotRocky; } status_t app_cache_app_launched(AppInstallId id) { return 0; } const PebbleProcessMd* system_app_state_machine_system_start(void) { return (PebbleProcessMd*) &s_launch_app; } const PebbleProcessMd* system_app_state_machine_get_last_registered_app(void) { return (PebbleProcessMd*) &s_root_app; } void system_app_state_machine_register_app_launch(const PebbleProcessMd* app) { } void sys_vibe_history_stop_collecting(void) { } Heap *worker_state_get_heap(void) { return NULL; } QueueHandle_t xQueueGenericCreate( unsigned portBASE_TYPE uxQueueLength, unsigned portBASE_TYPE uxItemSize, unsigned char ucQueueType ) { static intptr_t counter = 0; // Return unique IDs for all the created queues return (void*) ++counter; } signed portBASE_TYPE xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, portBASE_TYPE xCopyPosition ) { if (xQueue == app_manager_get_task_context()->to_process_event_queue) { s_last_to_app_event = *(PebbleEvent*) pvItemToQueue; } return pdTRUE; } BaseType_t event_queue_cleanup_and_reset(QueueHandle_t queue) { return pdPASS; } signed portBASE_TYPE xQueueGenericReceive( QueueHandle_t pxQueue, void * const pvBuffer, TickType_t xTicksToWait, portBASE_TYPE xJustPeeking ) { return pdTRUE; } BaseType_t xQueueGenericReset( QueueHandle_t xQueue, BaseType_t xNewQueue ) { return pdTRUE; } UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue ) { return 0; } void vQueueDelete( QueueHandle_t xQueue ) { } void watchface_set_default_install_id(AppInstallId id) { } void compositor_reset_app_framebuffer_ownership(void) { } const char *app_install_get_custom_app_name(AppInstallId install_id) { return NULL; } void status_bar_push_text(const char *text) { } const CompositorTransition* shell_get_open_compositor_animation(AppInstallId current_app_id, AppInstallId next_app_id) { return NULL; } const CompositorTransition* shell_get_close_compositor_animation(AppInstallId current_app_id, AppInstallId next_app_id) { return NULL; } void watchface_launch_default(const CompositorTransition *animation) { } void process_heap_set_exception_handlers(Heap *heap, const PebbleProcessMd *app_md) { } // Tests /////////////////////////////////////////////////////////// void test_app_manager__initialize(void) { process_manager_init(); app_manager_init(); s_last_to_app_event = (PebbleEvent) { }; s_app_task_control_reg = 0x1; // Just leave everything as unprivileged } void test_app_manager__start_first(void) { cl_assert(app_manager_get_current_app_md() == NULL); app_manager_start_first_app(); cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_launch_app); cl_assert(s_last_to_app_event.type == 0); app_manager_get_task_context()->safe_to_kill = false; } void test_app_manager__start_third_party(void) { test_app_manager__start_first(); app_manager_launch_new_app(&(AppLaunchConfig) { .md = &s_third_party_app.common, }); // We've sent the deinit event to the first app, but it's going to continue running. cl_assert_equal_i(s_last_to_app_event.type, PEBBLE_PROCESS_DEINIT_EVENT); cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_launch_app); s_last_to_app_event = (PebbleEvent) {0}; // Now the app sets the safe_to_kill flag to true and sends a kill event back to // the launcher to get the app killed again. This calls close_current_app, which ends // up launching s_third_party_app because it's in the next app slot. app_manager_get_task_context()->safe_to_kill = true; app_manager_close_current_app(true /* gracefully */); // The second app should now be running. cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_third_party_app); app_manager_get_task_context()->safe_to_kill = false; } void test_app_manager__start_third_party_and_crash_back_to_root(void) { test_app_manager__start_third_party(); // Simulate a crash app_manager_get_task_context()->safe_to_kill = true; app_manager_close_current_app(false /* gracefully */); cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_root_app); } void test_app_manager__start_borked_app(void) { test_app_manager__start_first(); app_manager_launch_new_app(&(AppLaunchConfig) { .md = &s_borked_app.common, }); app_manager_get_task_context()->safe_to_kill = true; app_manager_close_current_app(true /* gracefully */); // The first app should still be running cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_launch_app); } void test_app_manager__start_third_party_and_force_close_back_to_first(void) { test_app_manager__start_third_party(); s_last_to_app_event = (PebbleEvent) {0}; // Make the app get stuck in a syscall. This will indicate that the app is running // privileged. stub_control_reg(0x0); // Try to close the app. app_manager_close_current_app(true /* gracefully */); cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_third_party_app); cl_assert(s_last_to_app_event.type == PEBBLE_PROCESS_DEINIT_EVENT); // Simulate the deinit timer timing out instead of the app actually closing. app_manager_close_current_app(false /* gracefully */); // However it's still not ready to die. cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_third_party_app); // The trap has been set and eventually the syscall trap finds a good place to kill // the app. stub_control_reg(0x1); app_manager_close_current_app(false /* gracefully */); // The app should have exited to the root app. cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_root_app); } void test_app_manager__watchface_crash_on_close(void) { test_app_manager__start_first(); // Launch a new app with a panning animation. This will kick off the closing of the // current app. app_manager_launch_new_app(&(AppLaunchConfig) { .md = &s_third_party_app.common, }); // We've sent the deinit event to the first app, but it's going to continue running. cl_assert_equal_i(s_last_to_app_event.type, PEBBLE_PROCESS_DEINIT_EVENT); cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_launch_app); // However, the poor app is going to crash on the way out. app_manager_get_task_context()->safe_to_kill = true; app_manager_close_current_app(false /* gracefully */); // Make sure we correctly launch the root app with the right to left animation as opposed // to the panning animation we originally requested. cl_assert(app_manager_get_current_app_md() == (PebbleProcessMd*) &s_root_app); } void test_app_manager__override_next_app_with_watchface_using_action_performed_exit_reason(void) { test_app_manager__start_first(); // Check that the default exit reason is "not specified" const AppExitReason default_exit_reason = app_exit_reason_get(); cl_assert_equal_i(default_exit_reason, APP_EXIT_NOT_SPECIFIED); // Check that calling app_exit_reason_set() with an invalid exit reason does not change it app_exit_reason_set((AppExitReason)1337); cl_assert_equal_i(app_exit_reason_get(), default_exit_reason); app_exit_reason_set(NUM_EXIT_REASONS); cl_assert_equal_i(app_exit_reason_get(), default_exit_reason); app_exit_reason_set((AppExitReason)-1); cl_assert_equal_i(app_exit_reason_get(), default_exit_reason); // Specify the exit reason to be that an action was performed successfully app_exit_reason_set(APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY); // Check that closing the current app takes us to the watchface app_manager_get_task_context()->safe_to_kill = true; app_manager_close_current_app(true /* gracefully */); cl_assert_equal_b(app_manager_is_watchface_running(), true); // Check that launching a new app resets the exit reason to the default reason cl_assert_equal_b( app_manager_launch_new_app(&(AppLaunchConfig) { .md = &s_third_party_app.common, }), true); cl_assert_equal_i(app_exit_reason_get(), default_exit_reason); }