/* * 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 "process_manager.h" #include "app_install_manager.h" #include "app_manager.h" #include "worker_manager.h" #include "applib/app_logging.h" #include "applib/accel_service_private.h" #include "applib/platform.h" #include "applib/rockyjs/rocky_res.h" #include "applib/ui/dialogs/dialog.h" #include "applib/ui/dialogs/expandable_dialog.h" #include "process_state/app_state/app_state.h" #include "process_state/worker_state/worker_state.h" #include "pebble_process_md.h" #include "kernel/pebble_tasks.h" #include "os/tick.h" #include "resource/resource_ids.auto.h" #include "services/common/animation_service.h" #include "services/common/analytics/analytics.h" #include "services/common/evented_timer.h" #include "services/common/event_service.h" #include "services/common/hrm/hrm_manager.h" #include "services/normal/filesystem/pfs.h" #include "services/common/system_task.h" #include "services/normal/accessory/smartstrap_attribute.h" #include "services/normal/app_cache.h" #include "services/normal/data_logging/data_logging_service.h" #include "services/normal/persist.h" #include "services/normal/voice/voice.h" #include "shell/normal/watchface.h" #include "syscall/syscall.h" #include "system/logging.h" #include "system/passert.h" #include "kernel/pbl_malloc.h" #include "kernel/ui/modals/modal_manager.h" #include "util/heap.h" #include "syscall/syscall_internal.h" #include "apps/system_apps/app_fetch_ui.h" #include "FreeRTOS.h" #include "task.h" #include "queue.h" static TimerID s_deinit_timer_id = TIMER_INVALID_ID; // ------------------------------------------------------------------------------------------- static ProcessContext *prv_get_context_for_task(PebbleTask task) { if (task == PebbleTask_App) { return app_manager_get_task_context(); } else { PBL_ASSERTN(task == PebbleTask_Worker); return worker_manager_get_task_context(); } } // ------------------------------------------------------------------------------------------- static ProcessContext *prv_get_context(void) { return prv_get_context_for_task(pebble_task_get_current()); } // -------------------------------------------------------------------------------------------------- // This timer callback gets called if the process doesn't finish it's deinit within the required timeout (currently // 3 seconds). static void prv_graceful_close_timer_callback(void* data) { PBL_LOG(LOG_LEVEL_DEBUG, "deinit timeout expired, killing app forcefully"); PebbleTask task = (PebbleTask)data; process_manager_put_kill_process_event(task, false /*gracefully*/); } // --------------------------------------------------------------------------------------------- static bool prv_force_stop_task_if_unprivileged(ProcessContext *context) { vTaskSuspend((TaskHandle_t) context->task_handle); uint32_t control_reg = ulTaskDebugGetStackedControl((TaskHandle_t) context->task_handle); if ((control_reg & 0x1) == 0) { // We're privileged, it's not safe to just kill the app task. vTaskResume((TaskHandle_t) context->task_handle); return false; } context->safe_to_kill = true; return true; } // -------------------------------------------------------------------------------------------------- static void prv_force_close_timer_callback(void* data) { PebbleTask task = (PebbleTask)data; ProcessContext *context = prv_get_context_for_task(task); if (!prv_force_stop_task_if_unprivileged(context)) { PBL_CROAK("task stuck inside privileged code!"); } process_manager_put_kill_process_event(task, false /*graceful*/); } // --------------------------------------------------------------------------------------------- EXTERNALLY_VISIBLE void process_manager_handle_syscall_exit(void) { PebbleTask task = pebble_task_get_current(); ProcessContext *context = prv_get_context_for_task(task); if (context->closing_state == ProcessRunState_ForceClosing) { PBL_LOG(LOG_LEVEL_DEBUG, "Hit syscall exit trap!"); context->safe_to_kill = true; process_manager_put_kill_process_event(task, false); vTaskSuspend(xTaskGetCurrentTaskHandle()); } } // --------------------------------------------------------------------------------------------- void process_manager_init(void) { s_deinit_timer_id = new_timer_create(); } // ----------------------------------------------------------------------------------------------------------- void process_manager_put_kill_process_event(PebbleTask task, bool gracefully) { PebbleEvent event = { .type = PEBBLE_PROCESS_KILL_EVENT, .kill = { .gracefully = gracefully, .task = task, }, }; // When we have decided to exit the app, // it doesn't need to process any queued accel data // or other services before exiting, so clear the to_process_event_queue ProcessContext *context = prv_get_context_for_task(task); if (context->to_process_event_queue) { event_queue_cleanup_and_reset(context->to_process_event_queue); } // Since the app is about to exit, make sure the next (only) // message on the from app queue is the PEBBLE_APP_KILL_EVENT // to expedite exiting the application event_reset_from_process_queue(task); event_put_from_process(task, &event); } // --------------------------------------------------------------------------------------------- //! Init the context variables for a task. void process_manager_init_context(ProcessContext* context, const PebbleProcessMd *app_md, const void *args) { PBL_ASSERT_TASK(PebbleTask_KernelMain); PBL_ASSERTN(context->task_handle == NULL); PBL_ASSERTN(context->to_process_event_queue == NULL); context->app_md = app_md; AppInstallId install_id = app_install_get_id_for_uuid(&app_md->uuid); context->install_id = install_id; // we are safe to kill until the app main starts context->safe_to_kill = true; context->closing_state = ProcessRunState_Running; context->args = args; context->user_data = 0; // get app launch reason and wakeup_info context->launch_reason = app_manager_get_launch_reason(); context->launch_button = app_manager_get_launch_button(); context->wakeup_info = app_manager_get_app_wakeup_state(); // set the default exit reason context->exit_reason = APP_EXIT_NOT_SPECIFIED; } #if !defined(RECOVERY_FW) bool process_manager_check_SDK_compatible(const AppInstallId id) { AppInstallEntry entry; if (!app_install_get_entry_for_install_id(id, &entry)) { return false; } if (app_install_entry_is_SDK_compatible(&entry)) { return true; } PBL_LOG(LOG_LEVEL_WARNING, "App requires support for SDK version (%"PRIu8".%"PRIu8"), " "we only support version (%"PRIu8".%"PRIu8").", entry.sdk_version.major, entry.sdk_version.minor, (uint8_t) PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR, (uint8_t) PROCESS_INFO_CURRENT_SDK_VERSION_MINOR); ExpandableDialog *expandable_dialog = expandable_dialog_create("Incompatible SDK"); Dialog *dialog = expandable_dialog_get_dialog(expandable_dialog); const char *error_text = i18n_noop("This app requires a newer version of the Pebble firmware."); dialog_set_text(dialog, i18n_get(error_text, expandable_dialog)); dialog_set_icon(dialog, RESOURCE_ID_GENERIC_WARNING_SMALL); i18n_free(error_text, expandable_dialog); if (pebble_task_get_current() == PebbleTask_KernelMain) { WindowStack *window_stack = modal_manager_get_window_stack(ModalPriorityAlert); expandable_dialog_push(expandable_dialog, window_stack); } else { app_expandable_dialog_push(expandable_dialog); } return false; } static bool prv_needs_fetch(AppInstallId id, const PebbleProcessMd **md, bool is_worker) { PBL_ASSERTN(md); if (!app_cache_entry_exists(id)) { PBL_LOG(LOG_LEVEL_DEBUG, "Cache entry did not exist on launch attempt"); return true; } *md = app_install_get_md(id, is_worker); if (!is_worker && rocky_app_validate_resources(*md) == RockyResourceValidation_Invalid) { PBL_LOG(LOG_LEVEL_DEBUG, "App has incompatible JavaScript bytecode"); // TODO: do we need to purge the app cache here? return true; } return false; } #endif void process_manager_launch_process(const ProcessLaunchConfig *config) { PBL_ASSERT_TASK(PebbleTask_KernelMain); const AppInstallId id = config->id; const bool is_worker = config->worker; if (id == INSTALL_ID_INVALID) { PBL_LOG(LOG_LEVEL_DEBUG, "Invalid ID"); return; } const PebbleProcessMd *md = NULL; #if !RECOVERY_FW if (app_install_id_from_app_db(id)) { if (!process_manager_check_SDK_compatible(id)) { return; } // This is a third party flash 3.0 app install if (prv_needs_fetch(id, &md, is_worker)) { PBL_LOG(LOG_LEVEL_DEBUG, "Cache entry did not exist on launch attempt"); // Freed in app_fetch_ui.c AppFetchUIArgs *fetch_args = kernel_malloc_check(sizeof(AppFetchUIArgs)); *fetch_args = (AppFetchUIArgs){}; fetch_args->common = config->common; fetch_args->app_id = id; fetch_args->forcefully = config->forcefully; // if the data is wakeup info, then copy out that information. if ((config->common.reason == APP_LAUNCH_WAKEUP) && (config->common.args != NULL)) { fetch_args->wakeup_info = *(WakeupInfo *)config->common.args; fetch_args->common.args = &fetch_args->wakeup_info; } PebbleEvent e = { .type = PEBBLE_APP_FETCH_REQUEST_EVENT, .app_fetch_request = { .id = id, .with_ui = true, .fetch_args = fetch_args, }, }; event_put(&e); return; } else { // tell the app cache that we are launching this application. app_cache_app_launched(id); } } #endif // we either came here if PRF or if we didn't start a fetch // md is either already initialized, or we took a code path that didn't try if (!md) { md = app_install_get_md(id, is_worker); } if (!md) { PBL_LOG(LOG_LEVEL_ERROR, "Tried to launch non-existant app!"); return; } #if !RECOVERY_FW if (!is_worker) { // Check if the app ram size is valid in order to determine if its SDK version is supported. if (!app_manager_is_app_supported(md)) { PBL_LOG(LOG_LEVEL_WARNING, "Tried to launch an app with an unsupported SDK version."); AppInstallEntry entry; if (!app_install_get_entry_for_install_id(id, &entry)) { // can't retrieve app install entry for id PBL_LOG(LOG_LEVEL_ERROR, "Failed to get entry for id %"PRId32, id); } else if (app_install_entry_is_watchface(&entry)) { // If the watchface is for an unsupported SDK version, we need to switch the default // watchface back to tictoc. Otherwise, we will be stuck in the launcher forever. watchface_set_default_install_id(INSTALL_ID_INVALID); watchface_launch_default(NULL); } // Not going to launch this, release the allocated memory app_install_release_md(md); return; } } #endif if (is_worker) { worker_manager_launch_new_worker_with_args(md, NULL); } else { app_manager_launch_new_app(&(AppLaunchConfig) { .md = md, .common = config->common, .forcefully = config->forcefully, }); } } // --------------------------------------------------------------------------------------------- extern void analytics_external_collect_app_cpu_stats(void); extern void analytics_external_collect_app_flash_read_stats(void); static void prv_handle_app_stop_analytics(const ProcessContext *const context, PebbleTask task, bool gracefully) { if (!gracefully) { if (task == PebbleTask_App) { if (context->app_md->is_rocky_app) { analytics_inc(ANALYTICS_APP_METRIC_ROCKY_CRASHED_COUNT, AnalyticsClient_App); } analytics_inc(ANALYTICS_APP_METRIC_CRASHED_COUNT, AnalyticsClient_App); } else if (task == PebbleTask_Worker) { analytics_inc(ANALYTICS_APP_METRIC_BG_CRASHED_COUNT, AnalyticsClient_Worker); } if (context->app_md->is_rocky_app) { analytics_inc(ANALYTICS_DEVICE_METRIC_APP_ROCKY_CRASHED_COUNT, AnalyticsClient_System); } analytics_inc(ANALYTICS_DEVICE_METRIC_APP_CRASHED_COUNT, AnalyticsClient_System); } if (task == PebbleTask_App) { analytics_stopwatch_stop(ANALYTICS_APP_METRIC_FRONT_MOST_TIME); } analytics_external_collect_app_cpu_stats(); analytics_external_collect_app_flash_read_stats(); } // --------------------------------------------------------------------------------------------- //! This method returns true if the process is safe to kill (it has exited out of it's main function). If the //! the process is not already safe to kill, it will "prod" it to exit, set a timer, and return false. //! //! The app manager and worker manager MUST call this before they call the code to kill the task and clean it up //! (most of that work is done by process_manager_process_cleanup()). If it returns false, they should abort the //! current process exit operation and wait for another KILL event to get posted. //! //! If the task does eventually fall through it's main function, the exit handling code will set the safe to kill //! boolean and post another KILL event to the KernelMain which will result in this method being called again, and //! this time it will see the safe to kill is set and return true //! //! If the task does not exit by itself before the timer expires, then the timer will post another KILL event //! with graceful set to false. This will result in this method being called again with gracefully = false. When //! we see this, we just try and make sure the app is not stuck in privilege code. If it's not, we return true //! and allow the caller to kill the task. //! //! If however, the task is in privilege mode, we tell the syscall machinery to set the safe to kill boolean as //! soon as the current syscall returns and set another timer. Once that timer expires, if the task is no longer //! in privilege mode we post another KILL event (graceful = false). If the task is still in privilege mode then, //! we croak. bool process_manager_make_process_safe_to_kill(PebbleTask task, bool gracefully) { PBL_ASSERT_TASK(PebbleTask_KernelMain); ProcessContext *context = prv_get_context_for_task(task); // If already safe to kill, we're done if (context->safe_to_kill) { prv_handle_app_stop_analytics(context, task, gracefully); return true; } PBL_LOG(LOG_LEVEL_DEBUG, "make %s process safe to kill: state %u", pebble_task_get_name(task), context->closing_state); if (gracefully) { if (context->closing_state == ProcessRunState_Running) { context->closing_state = ProcessRunState_GracefullyClosing; PBL_LOG(LOG_LEVEL_DEBUG, "Attempting to gracefully deinit %s", pebble_task_get_name(task)); // Send deinit event to app: PebbleEvent deinit_event = { .type = PEBBLE_PROCESS_DEINIT_EVENT, }; process_manager_send_event_to_process(task, &deinit_event); // Set a timer to forcefully close the app in 3 seconds if it doesn't respond by then. The app can respond // within 3 seconds by posting a PEBBLE_APP_KILL_EVENT (graceful=true), which will result in // app_manager_close_current_app() being called, which in turn calls this method with graceful = true. bool success = new_timer_start(s_deinit_timer_id, 3 * 1000, prv_graceful_close_timer_callback, (void*)task, 0 /*flags*/); PBL_ASSERTN(success); } // Else we're already in the gracefully closing state, just let the timer run out or the // app to mark itself as safe_to_kill. } else { PBL_LOG(LOG_LEVEL_DEBUG, "Check if we can force stop the %s task", pebble_task_get_name(task)); if (prv_force_stop_task_if_unprivileged(context)) { PBL_LOG(LOG_LEVEL_DEBUG, "Got it"); prv_handle_app_stop_analytics(context, task, gracefully); return true; } // Non-graceful close if (context->closing_state == ProcessRunState_Running || context->closing_state == ProcessRunState_GracefullyClosing) { // Right before a syscall drops privilege, it calls // process_manager_force_close_syscall_exit_trap to check whether // it is about to return control to a misbehaving app. This // function checks the process context's closing state and makes // the process safe to kill if its state is set to ForceClosing. // All we have to do is set the state and wait. context->closing_state = ProcessRunState_ForceClosing; PBL_LOG(LOG_LEVEL_DEBUG, "task is privileged, setting the syscall exit trap"); bool success = new_timer_start(s_deinit_timer_id, 3 * 1000, prv_force_close_timer_callback, (void*)task, 0 /*flags*/); PBL_ASSERTN(success); } } return false; } // ----------------------------------------------------------------------------------------------------------- // This is designed to be called from the task itself, in privilege mode, after it exits. It is called from // app_task_exit for app tasks and worker_task_exit from worker tasks NORETURN process_manager_task_exit(void) { PebbleTask task = pebble_task_get_current(); ProcessContext *context = prv_get_context_for_task(task); // If this is not a system app, output its heap usage stats. if (context->app_md->process_storage == ProcessStorageFlash) { const Heap *heap; if (task == PebbleTask_App) { heap = app_state_get_heap(); } else if (task == PebbleTask_Worker) { heap = worker_state_get_heap(); } else { WTF; } // FIXME: We cast heap_size's size_t result to int because for some reason our printf doesn't // like the %zd formatter APP_LOG(APP_LOG_LEVEL_INFO, "Heap Usage for %s: Total Size <%dB> Used <%uB> Still allocated <%uB>", pebble_task_get_name(task), (int) heap_size(heap), heap->high_water_mark, heap->current_size); } // Let the task manager know we're done cleaning up. context->safe_to_kill = true; // Tell the task manager that we want to be killed. This may be redundant if we're responding to a DEINIT // message, but just in case we're exiting on our own (someone found the sys_exit syscall and called in when // we weren't expecting it?) we should let the app manager know. process_manager_put_kill_process_event(task, true); // Better to die in our sleep ... vTaskSuspend(NULL /* self */); // We don't expect someone to resume us. PBL_CROAK(""); } // --------------------------------------------------------------------------------------------- // Get the args for the current process const void *process_manager_get_current_process_args(void) { return prv_get_context()->args; } // --------------------------------------------------------------------------------------------- // Setup the system services required for this process. Called by app_manager and worker_manager // right before we launch the task for the new process. void process_manager_process_setup(PebbleTask task) { ProcessContext *context = prv_get_context_for_task(task); persist_service_client_open(&context->app_md->uuid); } // --------------------------------------------------------------------------------------------- //! Kills the process, giving it no chance to clean things up or exit gracefully. The process must already be in a //! state where it's safe to exit, so the caller must call process_manager_make_process_safe_to_kill() first and only //! call this method if process_manager_make_process_safe_to_kill() returns true; void process_manager_process_cleanup(PebbleTask task) { PBL_ASSERT_TASK(PebbleTask_KernelMain); ProcessContext *context = prv_get_context_for_task(task); PBL_ASSERTN(context->safe_to_kill); PBL_LOG(LOG_LEVEL_DEBUG, "%s is getting cleaned up", pebble_task_get_name(task)); // Shutdown services that may be running. Do this before we destroy the task and clear the queue // just in case other services are still in flight. accel_service_cleanup_task_session(task); animation_service_cleanup(task); persist_service_client_close(&context->app_md->uuid); event_reset_from_process_queue(task); evented_timer_clear_process_timers(task); event_service_clear_process_subscriptions(task); #if CAPABILITY_HAS_BUILTIN_HRM hrm_manager_process_cleanup(task, context->install_id); #endif #ifndef RECOVERY_FW #if CAPABILITY_HAS_MICROPHONE voice_kill_app_session(task); #endif dls_inactivate_sessions(task); if (task == PebbleTask_App) { #if CAPABILITY_HAS_ACCESSORY_CONNECTOR smartstrap_attribute_unregister_all(); #endif } #endif // RECOVERY_FW // Unregister the task pebble_task_unregister(task); new_timer_stop(s_deinit_timer_id); if (context->task_handle) { vTaskDelete(context->task_handle); context->task_handle = NULL; } // cleanup memory that was used to store the Md, but only if it isn't a system application app_install_release_md(context->app_md); // Clear the old app metadata context->app_md = 0; context->install_id = INSTALL_ID_INVALID; if (context->to_process_event_queue && pdFAIL == event_queue_cleanup_and_reset(context->to_process_event_queue)) { PBL_LOG(LOG_LEVEL_ERROR, "The to process queue could not be reset!"); } context->to_process_event_queue = NULL; } // ----------------------------------------------------------------------------------------------------------- void process_manager_close_process(PebbleTask task, bool gracefully) { if (task == PebbleTask_App) { // This will tell the app manager to switch to the last registered app. app_manager_close_current_app(gracefully); } else if (task == PebbleTask_Worker) { worker_manager_close_current_worker(gracefully); } else { WTF; } } // ---------------------------------------------------------------------------------------------- bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e) { ProcessContext *context = prv_get_context_for_task(task); if (context->to_process_event_queue == 0) { PBL_LOG(LOG_LEVEL_WARNING, "Dropped app event! Type: %u", e->type); return false; } // Put on app's own queue: if (!xQueueSend(context->to_process_event_queue, e, milliseconds_to_ticks(1000))) { PBL_LOG(LOG_LEVEL_ERROR, "Failed to send event %u to app! Closing it!", e->type); // We could be called from a timer task callback, so post a kill event rather than call // process_manager_close_process directly. process_manager_put_kill_process_event(task, false); return false; } return true; } // ---------------------------------------------------------------------------------------------- uint32_t process_manager_process_events_waiting(PebbleTask task) { ProcessContext *context = prv_get_context_for_task(task); if (context->to_process_event_queue == 0) { PBL_LOG(LOG_LEVEL_WARNING, "no event queue"); return 0; } return uxQueueMessagesWaiting(context->to_process_event_queue); } // ---------------------------------------------------------------------------------------------- void process_manager_send_callback_event_to_process(PebbleTask task, void (*callback)(void *data), void *data) { PBL_ASSERTN(callback != NULL); PebbleEvent event = { .type = PEBBLE_CALLBACK_EVENT, .callback = { .callback = callback, .data = data, }, }; process_manager_send_event_to_process(task, &event); } // ---------------------------------------------------------------------------------------------- void *process_manager_address_to_offset(PebbleTask task, void *system_address) { ProcessContext *context = prv_get_context_for_task(task); if (system_address >= context->load_start && system_address < context->load_end) { return (void*)((uintptr_t) system_address - (uintptr_t)context->load_start); } // Not in app space: return system_address; } // ---------------------------------------------------------------------------------------------- extern char __APP_RAM__[]; extern char __APP_RAM_end__[]; extern char __WORKER_RAM__[]; extern char __WORKER_RAM_end__[]; bool process_manager_is_address_in_region(PebbleTask task, const void *address, const void *lower_bound) { void *ram_start = NULL, *ram_end = NULL; if (task == PebbleTask_App) { ram_start = __APP_RAM__; ram_end = __APP_RAM_end__; } else if (task == PebbleTask_Worker) { ram_start = __WORKER_RAM__; ram_end = __WORKER_RAM_end__; } else { WTF; } // check for vulnerability: lower_bound outside of task's region PBL_ASSERTN(lower_bound >= ram_start); return (address >= lower_bound && address < ram_end); } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(void, sys_get_pebble_event, PebbleEvent *event) { if (PRIVILEGE_WAS_ELEVATED) { syscall_assert_userspace_buffer(event, sizeof(*event)); } xQueueReceive(prv_get_context()->to_process_event_queue, event, portMAX_DELAY); } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(AppLaunchReason, sys_process_get_launch_reason, void) { return prv_get_context()->launch_reason; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(ButtonId, sys_process_get_launch_button, void) { return prv_get_context()->launch_button; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(uint32_t, sys_process_get_launch_args, void) { if (sys_process_get_launch_reason() != APP_LAUNCH_TIMELINE_ACTION) { return 0; } else { return (uint32_t) process_manager_get_current_process_args(); } } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(AppExitReason, sys_process_get_exit_reason, void) { return prv_get_context()->exit_reason; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(void, sys_process_set_exit_reason, AppExitReason exit_reason) { // Just return if exit_reason is invalid if (exit_reason >= NUM_EXIT_REASONS) { return; } prv_get_context()->exit_reason = exit_reason; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(void, sys_process_get_wakeup_info, WakeupInfo *info) { if (PRIVILEGE_WAS_ELEVATED) { syscall_assert_userspace_buffer(info, sizeof(*info)); } *info = prv_get_context()->wakeup_info; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(const PebbleProcessMd*, sys_process_manager_get_current_process_md, void) { return prv_get_context()->app_md; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(bool, sys_process_manager_get_current_process_uuid, Uuid *uuid_out) { if (PRIVILEGE_WAS_ELEVATED) { syscall_assert_userspace_buffer(uuid_out, sizeof(*uuid_out)); } const PebbleProcessMd* app_md = prv_get_context()->app_md; if (!app_md) { return false; } *uuid_out = app_md->uuid; return true; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(AppInstallId, sys_process_manager_get_current_process_id, void) { return prv_get_context()->install_id; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(bool, process_manager_compiled_with_legacy2_sdk, void) { PebbleTask task = pebble_task_get_current(); if (task != PebbleTask_App && task != PebbleTask_Worker) { return false; } const ProcessAppSDKType sdk_type = process_metadata_get_app_sdk_type(sys_process_manager_get_current_process_md()); return sdk_type == ProcessAppSDKType_Legacy2x; } // ------------------------------------------------------------------------------------------- DEFINE_SYSCALL(bool, process_manager_compiled_with_legacy3_sdk, void) { PebbleTask task = pebble_task_get_current(); if (task != PebbleTask_App && task != PebbleTask_Worker) { return false; } const ProcessAppSDKType sdk_type = process_metadata_get_app_sdk_type(sys_process_manager_get_current_process_md()); return sdk_type == ProcessAppSDKType_Legacy3x; } DEFINE_SYSCALL(Version, sys_get_current_process_sdk_version, void) { return process_metadata_get_sdk_version(sys_process_manager_get_current_process_md()); } DEFINE_SYSCALL(PlatformType, process_manager_current_platform, void) { PebbleTask task = pebble_task_get_current(); if (task != PebbleTask_App && task != PebbleTask_Worker) { return PBL_PLATFORM_TYPE_CURRENT; } const PebbleProcessMd *const md = sys_process_manager_get_current_process_md(); return process_metadata_get_app_sdk_platform(md); }