pebble/tests/fw/test_app_manager.c
2025-01-27 11:38:16 -08:00

526 lines
15 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 "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);
}