mirror of
https://github.com/google/pebble.git
synced 2025-07-07 07:36:19 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
186
src/fw/process_management/app_custom_icon.c
Normal file
186
src/fw/process_management/app_custom_icon.c
Normal file
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* 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 "app_custom_icon.h"
|
||||
|
||||
#include "app_install_manager_private.h"
|
||||
#include "pebble_process_info.h"
|
||||
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "services/common/comm_session/session.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/math.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// We no longer have icons in the launcher, so we don't really need this anymore.
|
||||
// However, we may decide to put it back in, so let's keep the code around just in case.
|
||||
#define ALLOW_SET_ICON 0
|
||||
|
||||
typedef enum {
|
||||
SPORTS = 0x00,
|
||||
GOLF = 0x01,
|
||||
NUM_APP_TYPES,
|
||||
|
||||
UNKNOWN_APP_TYPE,
|
||||
APP_TYPE_MASK = 0x1,
|
||||
} CustomizableAppType;
|
||||
|
||||
typedef enum {
|
||||
NAME_FIELD = 0x00,
|
||||
ICON_FIELD = 0x80,
|
||||
FIELD_MASK = 0x80,
|
||||
} FieldId;
|
||||
|
||||
typedef struct PACKED {
|
||||
//! OR'ed value of (CustomizableAppType | FieldId). No C bitfields here, because order is compiler-specific.
|
||||
uint8_t app_type_and_field_bits;
|
||||
|
||||
union {
|
||||
char name[0];
|
||||
struct {
|
||||
uint16_t row_size_bytes;
|
||||
uint16_t info_flags; //<! See GBitmap::info_flags
|
||||
GRect bounds;
|
||||
uint8_t image_data[];
|
||||
} icon;
|
||||
};
|
||||
} AppCustomizeMessage;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
GBitmap icon;
|
||||
uint8_t *image_data;
|
||||
CustomizableAppType app_type:4;
|
||||
} AppCustomizeInfo;
|
||||
|
||||
static AppCustomizeInfo s_info[NUM_APP_TYPES];
|
||||
|
||||
static void do_callbacks(const CustomizableAppType app_type) {
|
||||
if (app_type == UNKNOWN_APP_TYPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppInstallId app_id = (app_type == SPORTS) ? APP_ID_SPORTS : APP_ID_GOLF;
|
||||
app_install_do_callbacks(APP_ICON_NAME_UPDATED, app_id, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
#if ALLOW_SET_ICON
|
||||
static void set_icon(const CustomizableAppType app_type, const uint16_t row_size_bytes,
|
||||
const uint16_t info_flags, const GRect bounds,
|
||||
const uint8_t image_data[], const uint16_t image_data_length) {
|
||||
|
||||
AppCustomizeInfo *info = &s_info[app_type];
|
||||
const uint16_t desired_size = row_size_bytes * bounds.size.h;
|
||||
const uint16_t available_size = MIN(desired_size, image_data_length);
|
||||
if (info->image_data &&
|
||||
memcmp(info->image_data, image_data, available_size) == 0 &&
|
||||
info->icon.bounds.origin.x == bounds.origin.x &&
|
||||
info->icon.bounds.origin.y == bounds.origin.y &&
|
||||
info->icon.bounds.size.w == bounds.size.w &&
|
||||
info->icon.bounds.size.h == bounds.size.h &&
|
||||
info->icon.info_flags == info_flags &&
|
||||
info->icon.row_size_bytes == row_size_bytes) {
|
||||
// Already equal to current icon
|
||||
return;
|
||||
}
|
||||
if (info->image_data) {
|
||||
kernel_free(info->image_data);
|
||||
}
|
||||
info->image_data = (uint8_t *) kernel_malloc(available_size);
|
||||
memcpy(info->image_data, image_data, available_size);
|
||||
info->icon.addr = info->image_data;
|
||||
info->icon.bounds = bounds;
|
||||
info->icon.row_size_bytes = row_size_bytes;
|
||||
info->icon.info_flags = info_flags;
|
||||
do_callbacks(app_type);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void set_name(const CustomizableAppType app_type, const char *name, const uint16_t length) {
|
||||
AppCustomizeInfo *info = &s_info[app_type];
|
||||
if (info->name &&
|
||||
strlen(info->name) == length &&
|
||||
strncmp(info->name, name, length) == 0) {
|
||||
// Already equal to current name
|
||||
return;
|
||||
}
|
||||
if (info->name) {
|
||||
kernel_free(info->name);
|
||||
}
|
||||
info->name = (char *) kernel_malloc(length + 1);
|
||||
memcpy(info->name, name, length);
|
||||
info->name[length] = 0;
|
||||
do_callbacks(app_type);
|
||||
}
|
||||
|
||||
void customizable_app_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t length) {
|
||||
AppCustomizeMessage *message = (AppCustomizeMessage *) data;
|
||||
const FieldId field_id = message->app_type_and_field_bits & FIELD_MASK;
|
||||
const CustomizableAppType app_type = message->app_type_and_field_bits & APP_TYPE_MASK;
|
||||
switch (field_id) {
|
||||
case NAME_FIELD: {
|
||||
const uint16_t name_length = length - offsetof(AppCustomizeMessage, name);
|
||||
set_name(app_type, message->name, name_length);
|
||||
break;
|
||||
}
|
||||
#if ALLOW_SET_ICON
|
||||
case ICON_FIELD: {
|
||||
const uint16_t image_data_length = length - offsetof(AppCustomizeMessage, icon.image_data);
|
||||
set_icon(app_type, message->icon.row_size_bytes, message->icon.info_flags, message->icon.bounds, message->icon.image_data, image_data_length);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default: return;
|
||||
}
|
||||
(void)session;
|
||||
}
|
||||
|
||||
static CustomizableAppType get_app_type_for_app_id(AppInstallId app_id) {
|
||||
if (app_id == APP_ID_SPORTS) {
|
||||
return SPORTS;
|
||||
} else if (app_id == APP_ID_GOLF) {
|
||||
return GOLF;
|
||||
}
|
||||
return UNKNOWN_APP_TYPE;
|
||||
}
|
||||
|
||||
// Retrieve the custom name or return NULL
|
||||
const char *app_custom_get_title(AppInstallId app_id) {
|
||||
CustomizableAppType app_type = get_app_type_for_app_id(app_id);
|
||||
|
||||
const char *name = NULL;
|
||||
if (app_type != UNKNOWN_APP_TYPE && s_info[app_type].name != NULL) {
|
||||
name = s_info[app_type].name;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
const GBitmap *app_custom_get_icon(AppInstallId app_id) {
|
||||
CustomizableAppType app_type = get_app_type_for_app_id(app_id);
|
||||
|
||||
if (app_type != UNKNOWN_APP_TYPE && s_info[app_type].image_data != NULL) {
|
||||
return &s_info[app_type].icon;
|
||||
}
|
||||
return NULL;
|
||||
}
|
30
src/fw/process_management/app_custom_icon.h
Normal file
30
src/fw/process_management/app_custom_icon.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//! @file app_custom_icon.h
|
||||
//! This file is part of app_install_manager.c
|
||||
//! It provides a Pebble protocol endpoint to allow 3rd party apps
|
||||
//! to customize the title and icon of certain stock apps, like the "Sports" app.
|
||||
|
||||
#include "pebble_process_md.h"
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "process_management/app_install_types.h"
|
||||
|
||||
const char *app_custom_get_title(AppInstallId app_id);
|
||||
|
||||
const GBitmap *app_custom_get_icon(AppInstallId app_id);
|
893
src/fw/process_management/app_install_manager.c
Normal file
893
src/fw/process_management/app_install_manager.c
Normal file
|
@ -0,0 +1,893 @@
|
|||
/*
|
||||
* 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 "app_install_manager.h"
|
||||
#include "app_install_manager_private.h"
|
||||
|
||||
#include "app_custom_icon.h"
|
||||
#include "app_manager.h"
|
||||
#include "worker_manager.h"
|
||||
|
||||
#include "applib/event_service_client.h"
|
||||
#include "apps/system_app_registry.h"
|
||||
#include "console/prompt.h"
|
||||
#include "drivers/task_watchdog.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "kernel/util/sleep.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/comm_session/app_session_capabilities.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/normal/app_cache.h"
|
||||
#include "services/normal/blob_db/app_db.h"
|
||||
#include "services/normal/blob_db/pin_db.h"
|
||||
#include "services/normal/persist.h"
|
||||
#include "services/normal/process_management/app_storage.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/circular_cache.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include <os/mutex.h>
|
||||
#include <util/attributes.h>
|
||||
|
||||
typedef struct PACKED RecentApp {
|
||||
AppInstallId id;
|
||||
time_t last_activity;
|
||||
bool can_expire;
|
||||
} RecentApp;
|
||||
|
||||
// The number of applications to store in the circular cache.
|
||||
// These are used to detect which application shave recently communicated
|
||||
#define NUM_RECENT_APPS 5
|
||||
#define CACHE_ENTRY_SIZE (sizeof(RecentApp))
|
||||
#define CACHE_BUFFER_SIZE (NUM_RECENT_APPS * CACHE_ENTRY_SIZE)
|
||||
#define RECENT_APP_LAST_ACTIVITY_INVALID (0)
|
||||
|
||||
typedef struct RecentAppCache {
|
||||
PebbleRecursiveMutex *mutex;
|
||||
CircularCache cache;
|
||||
uint8_t cache_buffer[CACHE_BUFFER_SIZE];
|
||||
} RecentAppCache;
|
||||
|
||||
static RecentAppCache s_recent_apps;
|
||||
|
||||
//! timeout for an app that has OnCommunication visibility (given in seconds)
|
||||
static int32_t VISIBILITY_ON_ACTIVITY_TIMEOUT_SECONDS = (5 * SECONDS_PER_MINUTE);
|
||||
|
||||
static bool prv_app_install_entry_from_app_db_entry(AppInstallId id, AppDBEntry *db_entry,
|
||||
AppInstallEntry *install_entry);
|
||||
|
||||
static AppInstallId s_pending_app_deletion;
|
||||
static AppInstallId s_pending_worker_deletion;
|
||||
|
||||
// PBL-31769: This should be moved to send_text.c
|
||||
#if !PLATFORM_TINTIN && defined(APP_ID_SEND_TEXT)
|
||||
static EventServiceInfo s_capabilities_event_info;
|
||||
#endif
|
||||
|
||||
//////////////////
|
||||
// Misc helpers
|
||||
//////////////////
|
||||
|
||||
static const AppRegistryEntry *prv_get_registry_list_entry(AppInstallId id,
|
||||
unsigned int *record_order_out) {
|
||||
if (app_install_id_from_app_db(id)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (int)ARRAY_LENGTH(APP_RECORDS); i++) {
|
||||
if (APP_RECORDS[i].id == id) {
|
||||
if (record_order_out) {
|
||||
*record_order_out = i + 1;
|
||||
}
|
||||
return &APP_RECORDS[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// optimization: sort the UUID's and then search more quickly. This will most likely
|
||||
// require many changes to the JSON generation script.
|
||||
AppInstallId app_get_install_id_for_uuid_from_registry(const Uuid *uuid) {
|
||||
for (uint16_t i = 0; i < ARRAY_LENGTH(APP_RECORDS); i++) {
|
||||
const AppRegistryEntry *reg_entry = &APP_RECORDS[i];
|
||||
|
||||
if (reg_entry->type == AppInstallStorageFw) {
|
||||
const PebbleProcessMd *md = reg_entry->md_fn();
|
||||
if (md && uuid_equal(&md->uuid, uuid)) {
|
||||
return reg_entry->id;
|
||||
}
|
||||
} else if ((reg_entry->type == AppInstallStorageResources)
|
||||
&& uuid_equal(®_entry->uuid, uuid)) {
|
||||
return reg_entry->id;
|
||||
}
|
||||
}
|
||||
return INSTALL_ID_INVALID;
|
||||
}
|
||||
|
||||
bool app_install_is_prioritized(AppInstallId install_id) {
|
||||
if (install_id == INSTALL_ID_INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rv = false;
|
||||
mutex_lock_recursive(s_recent_apps.mutex);
|
||||
{
|
||||
RecentApp *app = circular_cache_get(&s_recent_apps.cache, &install_id);
|
||||
if (app) {
|
||||
const int32_t time_since_activity = time_get_uptime_seconds() - app->last_activity;
|
||||
if (app->can_expire && (time_since_activity < VISIBILITY_ON_ACTIVITY_TIMEOUT_SECONDS)) {
|
||||
// The recent app should eventually expire and we are still below the threshold
|
||||
rv = true;
|
||||
} else if (!app->can_expire && app->last_activity != RECENT_APP_LAST_ACTIVITY_INVALID) {
|
||||
// The recent app should never expire and we haven't been manually expired yet.
|
||||
rv = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
mutex_unlock_recursive(s_recent_apps.mutex);
|
||||
return rv;
|
||||
}
|
||||
|
||||
void app_install_unmark_prioritized(AppInstallId install_id) {
|
||||
if (install_id == INSTALL_ID_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock_recursive(s_recent_apps.mutex);
|
||||
{
|
||||
RecentApp *app = circular_cache_get(&s_recent_apps.cache, &install_id);
|
||||
if (app) {
|
||||
app->last_activity = RECENT_APP_LAST_ACTIVITY_INVALID;
|
||||
}
|
||||
}
|
||||
mutex_unlock_recursive(s_recent_apps.mutex);
|
||||
}
|
||||
|
||||
void app_install_mark_prioritized(AppInstallId install_id, bool can_expire) {
|
||||
if (install_id == INSTALL_ID_INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_lock_recursive(s_recent_apps.mutex);
|
||||
{
|
||||
const time_t cur_time = time_get_uptime_seconds();
|
||||
RecentApp *app = circular_cache_get(&s_recent_apps.cache, &install_id);
|
||||
if (app) {
|
||||
app->last_activity = cur_time;
|
||||
app->can_expire = can_expire;
|
||||
} else {
|
||||
RecentApp app = {
|
||||
.id = install_id,
|
||||
.last_activity = cur_time,
|
||||
.can_expire = can_expire,
|
||||
};
|
||||
circular_cache_push(&s_recent_apps.cache, &app);
|
||||
}
|
||||
}
|
||||
mutex_unlock_recursive(s_recent_apps.mutex);
|
||||
}
|
||||
|
||||
#if UNITTEST
|
||||
void app_install_manager_flush_recent_communication_timestamps(void) {
|
||||
circular_cache_flush(&s_recent_apps.cache);
|
||||
memset(&s_recent_apps.cache_buffer, 0, sizeof(s_recent_apps.cache));
|
||||
}
|
||||
#endif
|
||||
|
||||
bool app_install_entry_is_watchface(const AppInstallEntry *entry) {
|
||||
return (entry->process_type == ProcessTypeWatchface);
|
||||
}
|
||||
|
||||
bool app_install_entry_has_worker(const AppInstallEntry *entry) {
|
||||
return (entry->has_worker);
|
||||
}
|
||||
|
||||
bool app_install_entry_is_hidden(const AppInstallEntry *entry) {
|
||||
switch (entry->visibility) {
|
||||
case ProcessVisibilityHidden:
|
||||
return true;
|
||||
case ProcessVisibilityShownOnCommunication:
|
||||
// make icon hidden (return true) if app has not recently communicated
|
||||
return !app_install_is_prioritized(entry->install_id);
|
||||
case ProcessVisibilityShown:
|
||||
return false;
|
||||
case ProcessVisibilityQuickLaunch:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool app_install_entry_is_quick_launch_visible_only(const AppInstallEntry *entry) {
|
||||
return (entry->visibility == ProcessVisibilityQuickLaunch);
|
||||
}
|
||||
|
||||
bool app_install_entry_is_SDK_compatible(const AppInstallEntry *entry) {
|
||||
return (entry->sdk_version.major == PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR &&
|
||||
entry->sdk_version.minor <= PROCESS_INFO_CURRENT_SDK_VERSION_MINOR);
|
||||
}
|
||||
|
||||
T_STATIC ListNode *s_head_callback_node_list = NULL;
|
||||
|
||||
void app_install_register_callback(struct AppInstallCallbackNode *callback_node) {
|
||||
PBL_ASSERTN(callback_node->node.next == NULL);
|
||||
PBL_ASSERTN(callback_node->node.prev == NULL);
|
||||
PBL_ASSERTN(s_head_callback_node_list != &callback_node->node);
|
||||
PBL_ASSERTN(callback_node->callbacks != NULL);
|
||||
|
||||
callback_node->registered_by = pebble_task_get_current();
|
||||
|
||||
s_head_callback_node_list = list_prepend(s_head_callback_node_list, &callback_node->node);
|
||||
}
|
||||
|
||||
void app_install_deregister_callback(struct AppInstallCallbackNode *callback_node) {
|
||||
PBL_ASSERTN(callback_node->node.next != NULL
|
||||
|| callback_node->node.prev != NULL
|
||||
|| s_head_callback_node_list == &callback_node->node);
|
||||
list_remove(&callback_node->node, &(s_head_callback_node_list), NULL);
|
||||
}
|
||||
|
||||
void app_install_cleanup_registered_app_callbacks(void) {
|
||||
struct AppInstallCallbackNode *iter = (struct AppInstallCallbackNode *) s_head_callback_node_list;
|
||||
while (iter) {
|
||||
if (iter->registered_by == PebbleTask_App) {
|
||||
list_remove((ListNode *)&iter->node, &s_head_callback_node_list, NULL);
|
||||
}
|
||||
iter = (struct AppInstallCallbackNode *) list_get_next(&iter->node);
|
||||
}
|
||||
}
|
||||
|
||||
static void app_install_invoke_callbacks(InstallEventType event_type, AppInstallId install_id) {
|
||||
struct AppInstallCallbackNode *callback_node = (struct AppInstallCallbackNode *) s_head_callback_node_list;
|
||||
while (callback_node) {
|
||||
if (callback_node->callbacks[event_type]) {
|
||||
callback_node->callbacks[event_type](install_id, callback_node->data);
|
||||
}
|
||||
callback_node = (struct AppInstallCallbackNode *) list_get_next(&callback_node->node);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
AppInstallEnumerateCb cb;
|
||||
void *data;
|
||||
AppInstallEntry *entry_buf;
|
||||
} EnumerateData;
|
||||
|
||||
static void prv_app_install_enumerate_app_db(AppInstallId install_id, AppDBEntry *db_entry,
|
||||
void *data) {
|
||||
EnumerateData *cb_data = (EnumerateData *)data;
|
||||
|
||||
prv_app_install_entry_from_app_db_entry(install_id, db_entry, cb_data->entry_buf);
|
||||
cb_data->cb(cb_data->entry_buf, cb_data->data);
|
||||
}
|
||||
|
||||
void app_install_enumerate_entries(AppInstallEnumerateCb cb, void *data) {
|
||||
// Keep this off of the stack. This function presses the limits of our stack.
|
||||
AppInstallEntry *entry = kernel_malloc_check(sizeof(AppInstallEntry));
|
||||
|
||||
// Iterate over the registry
|
||||
for (uint32_t i = 0; i < ARRAY_LENGTH(APP_RECORDS); i++) {
|
||||
if (app_install_get_entry_for_install_id(APP_RECORDS[i].id, entry)) {
|
||||
// if a false is returned from the function, then stop iterating.
|
||||
if (cb(entry, data) == false) {
|
||||
kernel_free(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over AppDB applications
|
||||
EnumerateData cb_data = {
|
||||
.cb = cb,
|
||||
.data = data,
|
||||
.entry_buf = entry,
|
||||
};
|
||||
app_db_enumerate_entries(prv_app_install_enumerate_app_db, &cb_data);
|
||||
|
||||
kernel_free(entry);
|
||||
}
|
||||
|
||||
AppInstallId app_install_get_id_for_uuid(const Uuid *uuid) {
|
||||
if (uuid_is_invalid(uuid) || uuid_is_system(uuid)) {
|
||||
// Don't allow lookups by system uuid, there will be a bunch of apps with that uuid
|
||||
return INSTALL_ID_INVALID;
|
||||
}
|
||||
|
||||
// search in system registry first, if found return the ID.
|
||||
AppInstallId id = app_get_install_id_for_uuid_from_registry(uuid);
|
||||
if (id != INSTALL_ID_INVALID) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// registry miss, now lets search in the app_db
|
||||
id = app_db_get_install_id_for_uuid(uuid);
|
||||
if (id != INSTALL_ID_INVALID) {
|
||||
return id;
|
||||
}
|
||||
|
||||
return INSTALL_ID_INVALID;
|
||||
}
|
||||
|
||||
static void prv_app_install_delete(AppInstallId id, Uuid *uuid, bool app_upgrade,
|
||||
bool delete_cache) {
|
||||
if (!app_upgrade) {
|
||||
// remove settings associated with the app
|
||||
pin_db_delete_with_parent(uuid);
|
||||
}
|
||||
|
||||
if (delete_cache) {
|
||||
// only log when we actually delete the cache entry. This is so we don't print out 100 logs
|
||||
// during an app cache clear
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Deleting app with id %"PRId32"", id);
|
||||
app_cache_remove_entry(id);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_delete_pending_id(AppInstallId *app_id) {
|
||||
if (*app_id != INSTALL_ID_INVALID) {
|
||||
// app cache will delete the app binaries even if the entry for the app_id does not exist
|
||||
app_cache_remove_entry(*app_id);
|
||||
*app_id = INSTALL_ID_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_process_pending_deletions(void) {
|
||||
prv_delete_pending_id(&s_pending_app_deletion);
|
||||
prv_delete_pending_id(&s_pending_worker_deletion);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Finished processing pending deletions");
|
||||
}
|
||||
|
||||
typedef struct InstallCallbackData {
|
||||
//! We can't have multiple callbacks in flight at once. Only invoke a new set of callbacks
|
||||
//! if this is false.
|
||||
bool callback_in_progress;
|
||||
|
||||
//! We may have to pause doing callbacks to wait for the app or worker to close. If so, this is set to
|
||||
//! true.
|
||||
bool callback_paused_for_app;
|
||||
bool callback_paused_for_worker;
|
||||
|
||||
InstallEventType install_type;
|
||||
|
||||
AppInstallId install_id;
|
||||
Uuid *uuid;
|
||||
|
||||
//! Callback to call when we're doing issuing this callback.
|
||||
InstallCallbackDoneCallback done_callback;
|
||||
|
||||
void* callback_data;
|
||||
} InstallCallbackData;
|
||||
|
||||
InstallCallbackData s_install_callback_data;
|
||||
|
||||
static bool prv_ids_equal(AppInstallId one, AppInstallId two) {
|
||||
return ((one == two) && (one != INSTALL_ID_INVALID));
|
||||
}
|
||||
|
||||
static void app_install_launcher_task_callback(void *context) {
|
||||
if (!s_install_callback_data.callback_paused_for_app &&
|
||||
!s_install_callback_data.callback_paused_for_worker) {
|
||||
// Only close the app the first time around.
|
||||
|
||||
if (s_install_callback_data.install_type == APP_UPGRADED ||
|
||||
s_install_callback_data.install_type == APP_REMOVED ||
|
||||
s_install_callback_data.install_type == APP_DB_CLEARED) {
|
||||
|
||||
const AppInstallId to_kill = s_install_callback_data.install_id;
|
||||
|
||||
// Close the current app if it is the one we are trying to remove/upgrade
|
||||
// OR
|
||||
// If we are doing an APP_DB_CLEAR and the currently running app is from the app_db,
|
||||
// also clear it.
|
||||
const AppInstallId cur_app_id = app_manager_get_current_app_id();
|
||||
if (prv_ids_equal(cur_app_id, to_kill) ||
|
||||
((s_install_callback_data.install_type == APP_DB_CLEARED) &&
|
||||
(app_install_id_from_app_db(cur_app_id)))) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "close and delay callbacks for app closing");
|
||||
|
||||
s_install_callback_data.callback_paused_for_app = true;
|
||||
s_pending_app_deletion = cur_app_id;
|
||||
app_manager_close_current_app(true);
|
||||
}
|
||||
|
||||
// Close the current worker if it is the one we are trying to remove/upgrade
|
||||
// OR
|
||||
// If we are doing an APP_DB_CLEAR and the currently running worker is from the app_db,
|
||||
// also clear it.
|
||||
const AppInstallId cur_worker_id = worker_manager_get_current_worker_id();
|
||||
if (prv_ids_equal(cur_worker_id, to_kill) ||
|
||||
((s_install_callback_data.install_type == APP_DB_CLEARED) &&
|
||||
(app_install_id_from_app_db(cur_worker_id)))) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "close and delay callbacks for worker closing");
|
||||
|
||||
s_install_callback_data.callback_paused_for_worker = true;
|
||||
s_pending_worker_deletion = cur_worker_id;
|
||||
worker_manager_handle_remove_current_worker();
|
||||
}
|
||||
|
||||
if (s_install_callback_data.callback_paused_for_app ||
|
||||
s_install_callback_data.callback_paused_for_worker) {
|
||||
// We're trying to remove or upgrade our currently running app. We now have
|
||||
// to wait until the app actually closes before continuing to notify the rest
|
||||
// of the system that we've removed or upgraded the app.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "app_install_invoke_callbacks");
|
||||
app_install_invoke_callbacks(s_install_callback_data.install_type,
|
||||
s_install_callback_data.install_id);
|
||||
|
||||
bool app_upgrade = false;
|
||||
|
||||
switch (s_install_callback_data.install_type) {
|
||||
case APP_UPGRADED:
|
||||
app_upgrade = true;
|
||||
/* fallthrough */
|
||||
case APP_REMOVED:
|
||||
prv_app_install_delete(s_install_callback_data.install_id, s_install_callback_data.uuid,
|
||||
app_upgrade, true /* delete cache entry */);
|
||||
// Only delete the app's persist file when the user explicitly removes the
|
||||
// app, not during an AppDB clear.
|
||||
if (!app_upgrade) {
|
||||
persist_service_delete_file(s_install_callback_data.uuid);
|
||||
comm_session_app_session_capabilities_evict(s_install_callback_data.uuid);
|
||||
}
|
||||
break;
|
||||
case APP_DB_CLEARED:
|
||||
prv_process_pending_deletions();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (s_install_callback_data.done_callback) {
|
||||
s_install_callback_data.done_callback(s_install_callback_data.callback_data);
|
||||
}
|
||||
|
||||
if (s_install_callback_data.uuid) {
|
||||
kernel_free(s_install_callback_data.uuid);
|
||||
}
|
||||
|
||||
s_install_callback_data = (InstallCallbackData) {
|
||||
.callback_in_progress = false
|
||||
};
|
||||
}
|
||||
|
||||
bool app_install_do_callbacks(InstallEventType event_type, AppInstallId install_id,
|
||||
Uuid *uuid, InstallCallbackDoneCallback done_callback, void* callback_data) {
|
||||
if (s_install_callback_data.callback_in_progress) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Failed to do app callbacks, already in progress");
|
||||
return false;
|
||||
}
|
||||
|
||||
s_install_callback_data = (InstallCallbackData) {
|
||||
.callback_in_progress = true,
|
||||
.install_id = install_id,
|
||||
.uuid = uuid,
|
||||
.install_type = event_type,
|
||||
.done_callback = done_callback,
|
||||
.callback_data = callback_data
|
||||
};
|
||||
|
||||
launcher_task_add_callback(app_install_launcher_task_callback, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *app_install_get_custom_app_name(AppInstallId install_id) {
|
||||
const char *name = app_custom_get_title(install_id);
|
||||
if (name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t app_install_entry_get_icon_resource_id(const AppInstallEntry *entry) {
|
||||
return entry->icon_resource_id;
|
||||
}
|
||||
|
||||
ResAppNum app_install_get_app_icon_bank(const AppInstallEntry *entry) {
|
||||
if (app_install_id_from_system(entry->install_id)) {
|
||||
return SYSTEM_APP;
|
||||
} else {
|
||||
return entry->install_id;
|
||||
}
|
||||
}
|
||||
|
||||
bool app_install_is_app_running(AppInstallId id) {
|
||||
return app_manager_get_task_context()->install_id == id;
|
||||
}
|
||||
|
||||
bool app_install_is_worker_running(AppInstallId id) {
|
||||
return worker_manager_get_task_context()->install_id == id;
|
||||
}
|
||||
|
||||
void app_install_notify_app_closed(void) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
// If we've previously paused doing app callbacks to wait for the app to close, resume them
|
||||
// now if the worker is also done
|
||||
if (s_install_callback_data.callback_paused_for_app) {
|
||||
if (!s_install_callback_data.callback_paused_for_worker) {
|
||||
app_install_launcher_task_callback(NULL);
|
||||
} else {
|
||||
s_install_callback_data.callback_paused_for_app = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_install_notify_worker_closed(void) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
// If we've previously paused doing app callbacks to wait for the app to close, resume them
|
||||
// now if the worker is also done
|
||||
if (s_install_callback_data.callback_paused_for_worker) {
|
||||
if (!s_install_callback_data.callback_paused_for_app) {
|
||||
app_install_launcher_task_callback(NULL);
|
||||
} else {
|
||||
s_install_callback_data.callback_paused_for_worker = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////
|
||||
// 3.0 Functions
|
||||
//////////////////
|
||||
|
||||
static int prv_cmp_recent_apps(void *a, void *b) {
|
||||
RecentApp *app_a = (RecentApp *)a;
|
||||
RecentApp *app_b = (RecentApp *)b;
|
||||
|
||||
return !(app_a->id == app_b->id);
|
||||
}
|
||||
|
||||
// PBL-31769: This should be moved to send_text.c
|
||||
#if !PLATFORM_TINTIN && defined(APP_ID_SEND_TEXT)
|
||||
static void prv_capabilities_changed_event_handler(PebbleEvent *event, void *context) {
|
||||
// We only care if send text support changed right now
|
||||
if (!event->capabilities.flags_diff.send_text_support) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PebbleProcessMd *md = app_install_get_md(APP_ID_SEND_TEXT, false /* worker */);
|
||||
const InstallEventType event_type = (md ? APP_AVAILABLE : APP_REMOVED);
|
||||
app_install_invoke_callbacks(event_type, APP_ID_SEND_TEXT);
|
||||
app_install_release_md(md);
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_install_manager_init(void) {
|
||||
circular_cache_init(&s_recent_apps.cache, s_recent_apps.cache_buffer, sizeof(RecentApp),
|
||||
NUM_RECENT_APPS, prv_cmp_recent_apps);
|
||||
s_recent_apps.mutex = mutex_create_recursive();
|
||||
|
||||
// PBL-31769: This should be moved to send_text.c
|
||||
#if !PLATFORM_TINTIN && defined(APP_ID_SEND_TEXT)
|
||||
s_capabilities_event_info = (EventServiceInfo) {
|
||||
.type = PEBBLE_CAPABILITIES_CHANGED_EVENT,
|
||||
.handler = prv_capabilities_changed_event_handler,
|
||||
};
|
||||
event_service_client_subscribe(&s_capabilities_event_info);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool app_install_id_from_system(AppInstallId id) {
|
||||
return (id < INSTALL_ID_INVALID);
|
||||
}
|
||||
|
||||
bool app_install_id_from_app_db(AppInstallId id) {
|
||||
return (id > INSTALL_ID_INVALID);
|
||||
}
|
||||
|
||||
static GColor prv_hard_coded_color_for_3rd_party_apps(Uuid *uuid) {
|
||||
|
||||
// Remove this from Recovery FW for code size savings.
|
||||
#if !defined(RECOVERY_FW) && !defined(PLATFORM_TINTIN)
|
||||
|
||||
// this is a temporary solution to enable custom colors for 3rd-party apps
|
||||
// please replace this, once PBL-19673 landed
|
||||
typedef struct {
|
||||
Uuid uuid;
|
||||
uint8_t color_argb;
|
||||
} ColorMapping;
|
||||
|
||||
static const ColorMapping mappings[] = {
|
||||
#include "app_install_manager_known_apps.h"
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(mappings); i++) {
|
||||
if (uuid_equal(uuid, &mappings[i].uuid)) {
|
||||
return (GColor){.argb = mappings[i].color_argb};
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return GColorClear;
|
||||
}
|
||||
|
||||
|
||||
static GColor prv_valid_color_from_uuid(GColor color, Uuid *uuid) {
|
||||
#if PLATFORM_TINTIN || PLATFORM_SILK
|
||||
return GColorClear;
|
||||
#endif
|
||||
|
||||
color = gcolor_closest_opaque(color);
|
||||
if (!gcolor_equal(color, GColorClear)) {
|
||||
return color;
|
||||
}
|
||||
|
||||
color = prv_hard_coded_color_for_3rd_party_apps(uuid);
|
||||
if (!gcolor_equal(color, GColorClear)) {
|
||||
return color;
|
||||
}
|
||||
|
||||
// if color isn't provided, build hash over uuid and pick from selected fall-back colors
|
||||
GColor fall_back_colors[] = {GColorFromHEX(0x0000aa), GColorFromHEX(0x005500),
|
||||
GColorFromHEX(0x550055), GColorFromHEX(0xff0055), GColorFromHEX(0xaa0000)};
|
||||
uint8_t uuid_byte_sum = 0;
|
||||
for (uint8_t *b = &uuid->byte0; b <= &uuid->byte15; b++) {
|
||||
uuid_byte_sum += *b;
|
||||
}
|
||||
return fall_back_colors[uuid_byte_sum % ARRAY_LENGTH(fall_back_colors)];
|
||||
}
|
||||
|
||||
static bool prv_app_install_entry_from_app_db_entry(AppInstallId id, AppDBEntry *db_entry,
|
||||
AppInstallEntry *entry) {
|
||||
|
||||
*entry = (AppInstallEntry) {
|
||||
.install_id = id,
|
||||
.type = AppInstallStorageFlash,
|
||||
.visibility = process_metadata_flags_visibility(db_entry->info_flags),
|
||||
// PebbleTask_App because the flag parsing function needs it, and we can assume all
|
||||
// applications registered with the manager are applications, not workers.
|
||||
.process_type = process_metadata_flags_process_type(db_entry->info_flags, PebbleTask_App),
|
||||
.has_worker = process_metadata_flags_has_worker(db_entry->info_flags),
|
||||
.icon_resource_id = db_entry->icon_resource_id,
|
||||
.uuid = db_entry->uuid,
|
||||
.color = prv_valid_color_from_uuid(db_entry->app_face_bg_color, (Uuid *)&db_entry->uuid),
|
||||
.sdk_version = db_entry->sdk_version,
|
||||
};
|
||||
|
||||
strncpy(entry->name, db_entry->name, APP_NAME_SIZE_BYTES);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_app_install_entry_from_resource_registry_entry(const AppRegistryEntry *reg_entry,
|
||||
AppInstallEntry *entry) {
|
||||
PebbleProcessInfo *app_header = kernel_malloc_check(sizeof(PebbleProcessInfo));
|
||||
bool rv = false;
|
||||
|
||||
if (resource_load_byte_range_system(SYSTEM_APP, reg_entry->bin_resource_id, 0,
|
||||
(uint8_t *)app_header, sizeof(*app_header)) != sizeof(*app_header)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Stored app with resource id %d not found in resources",
|
||||
reg_entry->bin_resource_id);
|
||||
goto done;
|
||||
}
|
||||
*entry = (AppInstallEntry) {
|
||||
.install_id = reg_entry->id,
|
||||
.type = AppInstallStorageResources,
|
||||
.visibility = process_metadata_flags_visibility(app_header->flags),
|
||||
// PebbleTask_App because the flag parsing function needs it, and we can assume all
|
||||
// applications registered with the manager are applications, not workers.
|
||||
.process_type = process_metadata_flags_process_type(app_header->flags, PebbleTask_App),
|
||||
.has_worker = process_metadata_flags_has_worker(app_header->flags),
|
||||
.icon_resource_id = reg_entry->icon_resource_id,
|
||||
.uuid = reg_entry->uuid,
|
||||
.color = prv_valid_color_from_uuid(reg_entry->color, (Uuid *) ®_entry->uuid),
|
||||
.sdk_version = app_header->sdk_version,
|
||||
};
|
||||
|
||||
i18n_get_with_buffer(reg_entry->name, entry->name, APP_NAME_SIZE_BYTES);
|
||||
rv = true;
|
||||
done:
|
||||
kernel_free(app_header);
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool prv_app_install_entry_from_fw_registry_entry(const AppRegistryEntry *reg_entry,
|
||||
AppInstallEntry *entry) {
|
||||
|
||||
const PebbleProcessMdSystem *md = (PebbleProcessMdSystem *) reg_entry->md_fn();
|
||||
|
||||
if (!md) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*entry = (AppInstallEntry) {
|
||||
.install_id = reg_entry->id,
|
||||
.type = AppInstallStorageFw,
|
||||
.visibility = md->common.visibility,
|
||||
.process_type = md->common.process_type,
|
||||
.has_worker = md->common.has_worker,
|
||||
.icon_resource_id = md->icon_resource_id,
|
||||
.uuid = md->common.uuid,
|
||||
.color = prv_valid_color_from_uuid(reg_entry->color, (Uuid *) &md->common.uuid),
|
||||
.sdk_version = process_metadata_get_sdk_version((PebbleProcessMd *)md),
|
||||
};
|
||||
|
||||
i18n_get_with_buffer(md->name, entry->name, APP_NAME_SIZE_BYTES);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool app_install_get_entry_for_install_id(AppInstallId install_id, AppInstallEntry *entry) {
|
||||
if ((install_id == INSTALL_ID_INVALID) || (entry == NULL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int record_order = 0;
|
||||
const AppRegistryEntry *reg_entry = prv_get_registry_list_entry(install_id, &record_order);
|
||||
if (reg_entry) {
|
||||
bool rv = false;
|
||||
// switch on registry type
|
||||
switch (reg_entry->type) {
|
||||
case AppInstallStorageFw:
|
||||
rv = prv_app_install_entry_from_fw_registry_entry(reg_entry, entry);
|
||||
break;
|
||||
case AppInstallStorageResources:
|
||||
rv = prv_app_install_entry_from_resource_registry_entry(reg_entry, entry);
|
||||
break;
|
||||
case AppInstallStorageInvalid:
|
||||
case AppInstallStorageFlash:
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Invalid app registry type %d", reg_entry->type);
|
||||
break;
|
||||
}
|
||||
if (rv) {
|
||||
entry->record_order = record_order;
|
||||
}
|
||||
return rv;
|
||||
} else if (app_db_exists_install_id(install_id)) {
|
||||
AppDBEntry *db_entry = kernel_malloc_check(sizeof(AppDBEntry));
|
||||
bool rv = (app_db_get_app_entry_for_install_id(install_id, db_entry) == S_SUCCESS);
|
||||
if (rv) {
|
||||
rv = prv_app_install_entry_from_app_db_entry(install_id, db_entry, entry);
|
||||
}
|
||||
kernel_free(db_entry);
|
||||
return rv;
|
||||
}
|
||||
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Failed to get entry for id %"PRId32, install_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool app_install_get_uuid_for_install_id(AppInstallId install_id, Uuid *uuid_out) {
|
||||
PBL_ASSERTN(uuid_out);
|
||||
AppInstallEntry entry;
|
||||
if (app_install_get_entry_for_install_id(install_id, &entry)) {
|
||||
*uuid_out = entry.uuid;
|
||||
return true;
|
||||
} else {
|
||||
*uuid_out = UUID_INVALID;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool app_install_is_watchface(AppInstallId app_id) {
|
||||
AppInstallEntry entry;
|
||||
if (!app_install_get_entry_for_install_id(app_id, &entry)) {
|
||||
return false;
|
||||
}
|
||||
return app_install_entry_is_watchface(&entry);
|
||||
}
|
||||
|
||||
static const PebbleProcessMd *prv_get_md_for_reg_entry(const AppRegistryEntry *reg_entry) {
|
||||
switch (reg_entry->type) {
|
||||
case AppInstallStorageFw:
|
||||
// If its a FW app, just return the Md
|
||||
return reg_entry->md_fn();
|
||||
case AppInstallStorageResources: {
|
||||
// If its a RESOURCE app, we much read from the resource pack and populate an Md
|
||||
PebbleProcessInfo app_header;
|
||||
if (resource_load_byte_range_system(SYSTEM_APP, reg_entry->bin_resource_id, 0,
|
||||
(uint8_t *)&app_header, sizeof(app_header)) != sizeof(app_header)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Stored app with resource id %d not found in resources",
|
||||
reg_entry->bin_resource_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Convert to PebbleProcessMd. Set the correct icon_id from the passed in argument
|
||||
app_header.icon_resource_id = reg_entry->icon_resource_id;
|
||||
// freed in process_manager.c
|
||||
PebbleProcessMdResource *md = kernel_malloc_check(sizeof(PebbleProcessMdResource));
|
||||
process_metadata_init_with_resource_header(md, &app_header, reg_entry->bin_resource_id,
|
||||
PebbleTask_App);
|
||||
const PebbleProcessMd *const_md = (PebbleProcessMd *)md;
|
||||
return const_md;
|
||||
}
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const PebbleProcessMd *prv_get_md_for_flash_id(AppInstallId id, bool worker) {
|
||||
#ifdef RECOVERY_FW
|
||||
return NULL;
|
||||
#endif
|
||||
|
||||
PebbleProcessInfo app_header;
|
||||
uint8_t build_id_buffer[BUILD_ID_EXPECTED_LEN];
|
||||
const PebbleTask task = worker ? PebbleTask_Worker : PebbleTask_App;
|
||||
if (GET_APP_INFO_SUCCESS !=
|
||||
app_storage_get_process_info(&app_header, build_id_buffer, id, task)) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Failed to get app from flash with id %"PRIu32, id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// freed in process_manager.c
|
||||
PebbleProcessMdFlash *md = kernel_malloc_check(sizeof(PebbleProcessMdFlash));
|
||||
process_metadata_init_with_flash_header(md, &app_header, id, task, build_id_buffer);
|
||||
const PebbleProcessMd *const_md = (PebbleProcessMd *)md;
|
||||
return const_md;
|
||||
}
|
||||
|
||||
|
||||
// PebbleProcessMd is freed in process_manager.c when the application quits
|
||||
const PebbleProcessMd *app_install_get_md(AppInstallId id, bool worker) {
|
||||
const AppRegistryEntry *reg_entry = prv_get_registry_list_entry(id, NULL /* record_order */);
|
||||
if (reg_entry) {
|
||||
return prv_get_md_for_reg_entry(reg_entry);
|
||||
} else if (app_db_exists_install_id(id)) {
|
||||
return prv_get_md_for_flash_id(id, worker);
|
||||
}
|
||||
|
||||
// Not a registered app, fail.
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Can't get PebbleProcessMd for app id %"PRId32, id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void app_install_release_md(const PebbleProcessMd *md) {
|
||||
if (!md) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (md->process_storage) {
|
||||
case ProcessStorageBuiltin:
|
||||
break;
|
||||
case ProcessStorageFlash:
|
||||
case ProcessStorageResource:
|
||||
kernel_free((PebbleProcessMd*) md);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_enumerate_app_db_delete(AppInstallId install_id, AppDBEntry *db_entry,
|
||||
void *data) {
|
||||
PBL_ASSERTN(app_install_id_from_app_db(install_id));
|
||||
task_watchdog_bit_set(pebble_task_get_current());
|
||||
|
||||
const bool gracefully = true;
|
||||
if (app_manager_get_current_app_id() == install_id) {
|
||||
process_manager_put_kill_process_event(PebbleTask_App, gracefully);
|
||||
}
|
||||
|
||||
if (worker_manager_get_current_worker_id() == install_id) {
|
||||
process_manager_put_kill_process_event(PebbleTask_Worker, gracefully);
|
||||
}
|
||||
|
||||
// We are not deleting the cache here because it will be deleted quicker in filesystem iteration
|
||||
// This way, it can clean up much quicker than searching through the filesystem every time
|
||||
const bool app_upgrade = false;
|
||||
const bool delete_cache = false;
|
||||
prv_app_install_delete(install_id, &db_entry->uuid, app_upgrade, delete_cache);
|
||||
}
|
||||
|
||||
void app_install_clear_app_db(void) {
|
||||
app_db_enumerate_entries(prv_enumerate_app_db_delete, NULL);
|
||||
app_cache_flush();
|
||||
}
|
264
src/fw/process_management/app_install_manager.h
Normal file
264
src/fw/process_management/app_install_manager.h
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "app_install_types.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "applib/graphics/gcolor_definitions.h"
|
||||
#include "drivers/rtc.h"
|
||||
#include "kernel/events.h"
|
||||
#include "pebble_process_md.h"
|
||||
#include "resource/resource.h"
|
||||
#include "util/list.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// This module is responsible for keeping track of what apps are installed,
|
||||
// and acts as the abstraction which makes system and third party apps look the same
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//! There are many different representations, structures, and identifiers of an Application within
|
||||
//! our firmware. This is an attempt to clear up the different types and also to document them.
|
||||
//!
|
||||
//! Accessing apps or their metadata
|
||||
//! ----------------------------------
|
||||
//! Preferred Methods
|
||||
//!
|
||||
//! - AppInstallEntry: Universal struct that contains all metadata for an application no matter
|
||||
//! where it originates from (FW, Resource, Flash)
|
||||
//! - AppInstallId: A number assigned to an application. Pre-assigned and negative for FW and
|
||||
//! resource applications, assigned on install for Flash applications.
|
||||
//!
|
||||
//! Deprecated Methods
|
||||
//!
|
||||
//! - Uuid: a 16-byte identifier for an application. Every application process must contain a Uuid,
|
||||
//! but it is not a requirement for firmware applications. Should avoid whenever possible.
|
||||
//! A Uuid should only be used when in communication with the phone, since the phone should
|
||||
//! have no knowledge of AppInstallId's.
|
||||
//!
|
||||
//! - PebbleProcessMd: This should only be used when launching an application. No piece of code
|
||||
//! should ask the app_install_manager for a PebbleProcessMd unlesss it plans on
|
||||
//! assisting with the launch of the application (event_loop, app_manager, etc.)
|
||||
//!
|
||||
//! How applications are stored
|
||||
//! ---------------------------
|
||||
//! Serialized Data Structures
|
||||
//!
|
||||
//! - AppDBEntry: The phone sends over this packed data in BlobDB and is stored in flash on the
|
||||
//! watch. This data gets deserialized in the AppInstallManager and morphed into an
|
||||
//! AppInstallEntry. This structure can't change without agreeing with the phone on
|
||||
//! the changes. These entries are assigned an AppInstallId on install.
|
||||
//!
|
||||
//! - AppRegistryEntry: Hardcoded metadata for system application that comes packaged with the
|
||||
//! firmware. There are two types: FW and RESOURCE. These entries are assigned
|
||||
//! an AppInstallId by the programmer. Hardcoded and should never change.
|
||||
//! - FW: These are the applications that have hard coded PebbleProcessMd's.
|
||||
//! - RESOURCE: These are stored in the System Resource Pack and loaded on demand.
|
||||
//!
|
||||
//! Misc Notes
|
||||
//! ----------
|
||||
//!
|
||||
//! If any module wants information about an application, the process is as follows:
|
||||
//! 1. It should first retrieve an AppInstallId.
|
||||
//! 2. Call app_install_get_entry_for_install_id and get the entry data structure
|
||||
//! 3. Use the getter functions for the entry to retrieve individual fields within the struct.
|
||||
|
||||
|
||||
#define TIMESTAMP_INVALID ((RtcTicks)0) //!< for most_recent_communication_timestamp in AppInstallEntry
|
||||
|
||||
//! Max number of bytes for an application.
|
||||
#define APP_NAME_SIZE_BYTES 96
|
||||
|
||||
typedef enum {
|
||||
AppInstallStorageInvalid = 0,
|
||||
AppInstallStorageFw = 1,
|
||||
AppInstallStorageFlash = 2,
|
||||
AppInstallStorageResources = 3,
|
||||
} AppInstallStorage;
|
||||
|
||||
typedef struct {
|
||||
AppInstallId install_id;
|
||||
AppInstallStorage type:2; // SYSTEM/RESOURCE/FLASH
|
||||
ProcessVisibility visibility;
|
||||
ProcessType process_type; // WATCHFACE/APP
|
||||
bool has_worker;
|
||||
Uuid uuid;
|
||||
GColor color;
|
||||
char name[APP_NAME_SIZE_BYTES];
|
||||
int icon_resource_id;
|
||||
Version sdk_version;
|
||||
unsigned int record_order; //!< 0 means not in the app registry
|
||||
} AppInstallEntry;
|
||||
|
||||
typedef enum {
|
||||
APP_AVAILABLE = 0, //< occurs on app installation
|
||||
APP_REMOVED = 1, //< occurs on app removal
|
||||
APP_ICON_NAME_UPDATED = 2, //< occurs when app (metadata) has been updated
|
||||
APP_UPGRADED = 3, //< occurs when app is getting removed prior to upgrade
|
||||
APP_DB_CLEARED = 4, //< occurs when app is getting removed prior to upgrade
|
||||
NUM_INSTALL_EVENT_TYPES,
|
||||
} InstallEventType;
|
||||
|
||||
//! Used for the static application entries in the app registry
|
||||
typedef const PebbleProcessMd* (*MdFunc) (void);
|
||||
|
||||
//! Used for apps listed in the system app registry
|
||||
typedef struct {
|
||||
AppInstallId id;
|
||||
AppInstallStorage type;
|
||||
GColor color;
|
||||
union {
|
||||
MdFunc md_fn;
|
||||
struct {
|
||||
const char *name;
|
||||
Uuid uuid;
|
||||
int bin_resource_id;
|
||||
int icon_resource_id;
|
||||
};
|
||||
};
|
||||
} AppRegistryEntry;
|
||||
|
||||
void app_install_manager_init(void);
|
||||
|
||||
//! Get AppInstallId for the provided Uuid
|
||||
//! @param uuid Uuid to convert to an AppInstallId
|
||||
//! @return valid AppInstallId or INSTALL_ID_INVALID
|
||||
AppInstallId app_install_get_id_for_uuid(const Uuid *uuid);
|
||||
|
||||
//! Search the system registry for the AppInstallId for the provided Uuid
|
||||
//! @param uuid Uuid to convert to an AppInstallId
|
||||
//! @return valid AppInstallId or INSTALL_ID_INVALID if not a built-in app
|
||||
AppInstallId app_get_install_id_for_uuid_from_registry(const Uuid *uuid);
|
||||
|
||||
//! Subscription callback
|
||||
typedef void (*AppInstallCallback)(const AppInstallId install_id, void *data);
|
||||
|
||||
typedef struct AppInstallCallbackNode {
|
||||
ListNode node;
|
||||
//! Must point to data that lives at least until app_install_deregister_callback() is called:
|
||||
void *data;
|
||||
//! Array of callbacks for each event type:
|
||||
const AppInstallCallback *callbacks;
|
||||
PebbleTask registered_by;
|
||||
} AppInstallCallbackNode;
|
||||
|
||||
//! Registers callbacks for add/remove/change events from the app install manager
|
||||
//! @note Callbacks are invoked on the launcher task!
|
||||
void app_install_register_callback(struct AppInstallCallbackNode *callback_info);
|
||||
|
||||
//! Deregisters callbacks for add/remove/change events from the app install manager
|
||||
void app_install_deregister_callback(struct AppInstallCallbackNode *callback_info);
|
||||
|
||||
//! Deregisters callbacks that were registered on the app task
|
||||
void app_install_cleanup_registered_app_callbacks(void);
|
||||
|
||||
//! Generates an AppInstallEntry for the install_id given and reads it into the given entry
|
||||
//! pointer.
|
||||
//!
|
||||
//! @note If any piece of code wants to read any characteristics of an installed application,
|
||||
//! it should first get an entry then call the below operations to read the fields of the
|
||||
//! struct.
|
||||
//! @param install_id AppInstallId of the application
|
||||
//! @param entry AppInstallEntry buffer to write to
|
||||
//! @return return True if the entry was successfully retrieved and written to the buffer. Will
|
||||
//! return false if the ID is invalid or does not exist, or if entry is NULL.
|
||||
bool app_install_get_entry_for_install_id(AppInstallId install_id, AppInstallEntry *entry);
|
||||
|
||||
//! Gets the corresponding Uuid given an AppInstallId. This loads an entry to obtain its
|
||||
//! information, use the entry directly if your context has an AppInstallEntry.
|
||||
//! @param install_id AppInstallId of the application
|
||||
//! @param uuid_out pointer to a Uuid buffer where the application Uuid will be written
|
||||
//! @return false if the ID is invalid or does not exist, true otherwise
|
||||
bool app_install_get_uuid_for_install_id(AppInstallId install_id, Uuid *uuid_out);
|
||||
|
||||
//! Gets whether the app is a watchface given an AppInstallId. This loads an entry to obtain its
|
||||
//! information, use \ref app_install_entry_is_watchface if your context has an AppInstallEntry.
|
||||
//! @param install_id AppInstallId of the application
|
||||
//! @return true if the app is a watchface, false otherwise
|
||||
bool app_install_is_watchface(AppInstallId app_id);
|
||||
|
||||
//! Returns true if the app associated with the provided entry is a watchface
|
||||
//! @param entry AppInstallEntry to check the parameters of
|
||||
bool app_install_entry_is_watchface(const AppInstallEntry *entry);
|
||||
|
||||
//! Returns true if the app associated with the provided entry has a worker
|
||||
//! @param entry AppInstallEntry to check the parameters of
|
||||
bool app_install_entry_has_worker(const AppInstallEntry *entry);
|
||||
|
||||
//! Returns true if the app associated with the provided entry should be hidden in menus
|
||||
//! @param entry AppInstallEntry to check the parameters of
|
||||
bool app_install_entry_is_hidden(const AppInstallEntry *entry);
|
||||
|
||||
//! Gets whether the app is visible in the list of apps that can be set as a quick launch shortcut.
|
||||
//! @return true if the app is only visible in quick launch, false otherwise
|
||||
bool app_install_entry_is_quick_launch_visible_only(const AppInstallEntry *entry);
|
||||
|
||||
//! Returns true if the app associated with the provided entry is SDK compatible
|
||||
//! @param entry AppInstallEntry to check the parameters of
|
||||
bool app_install_entry_is_SDK_compatible(const AppInstallEntry *entry);
|
||||
|
||||
typedef bool(*AppInstallEnumerateCb)(AppInstallEntry *entry, void *data);
|
||||
|
||||
//! Enumerates all active install ids for non-hidden apps and calls the given function for each
|
||||
//! install id.
|
||||
//! @param cb Pointer to a function that will get called once for each app install id.
|
||||
//! This callback can return false to end the enumeration prematurely, or true to continue.
|
||||
//! @param data Pointer to arbitrary user data that will get passed into the function
|
||||
void app_install_enumerate_entries(AppInstallEnumerateCb cb, void *data);
|
||||
|
||||
void app_install_clear_app_db(void);
|
||||
|
||||
//! These functions are not reading characteristics of the AppInstallEntry so they do not
|
||||
//! require an entry to be passed in.
|
||||
bool app_install_is_app_running(AppInstallId id);
|
||||
|
||||
bool app_install_is_worker_running(AppInstallId id);
|
||||
|
||||
void app_install_notify_app_closed(void);
|
||||
|
||||
void app_install_notify_worker_closed(void);
|
||||
|
||||
//! @param id The ID for the desired MD
|
||||
//! @param worker True if we want the worker MD, false if we want the app MD
|
||||
//! @return Returns a pointer to the PebbleProcessMd. When the caller is done with this pointer
|
||||
//! they should call app_install_md_release to release the associated memory.
|
||||
const PebbleProcessMd *app_install_get_md(AppInstallId id, bool worker);
|
||||
|
||||
//! Release a md that was previously allocated by app_install_get_md.
|
||||
void app_install_release_md(const PebbleProcessMd *md);
|
||||
|
||||
//! Retrieves the custom name for an application if it has sent a new application name
|
||||
const char *app_install_get_custom_app_name(AppInstallId install_id);
|
||||
|
||||
bool app_install_is_prioritized(AppInstallId install_id);
|
||||
|
||||
void app_install_mark_prioritized(AppInstallId install_id, bool can_expire);
|
||||
|
||||
void app_install_unmark_prioritized(AppInstallId install_id);
|
||||
|
||||
#if UNITTEST
|
||||
void app_install_manager_flush_recent_communication_timestamps(void);
|
||||
#endif
|
||||
|
||||
//////////////////////
|
||||
// Deprecated
|
||||
// = Will remove once launcher is reimplemented
|
||||
//////////////////////
|
||||
|
||||
uint32_t app_install_entry_get_icon_resource_id(const AppInstallEntry *entry);
|
||||
|
||||
ResAppNum app_install_get_app_icon_bank(const AppInstallEntry *entry);
|
83
src/fw/process_management/app_install_manager_known_apps.h
Normal file
83
src/fw/process_management/app_install_manager_known_apps.h
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// @nolint
|
||||
// please don't change these values manually, they are derived from the spreadsheet
|
||||
// "2015 Partner Planning - list of color for apps" from the bizdev team
|
||||
|
||||
{.uuid = {0xe0, 0x89, 0x86, 0x19, 0xec, 0xcd, 0x43, 0x70, 0x91, 0x41, 0x2c, 0xe1, 0x9b, 0x91, 0xc4, 0x32}, .color_argb = GColorRedARGB8}, // Yelp
|
||||
{.uuid = {0xd6, 0xdb, 0xcb, 0x9f, 0x06, 0x14, 0x48, 0x98, 0x99, 0x8c, 0xb7, 0xb6, 0x3e, 0x7c, 0xe5, 0x7c}, .color_argb = GColorRajahARGB8}, // Swarm
|
||||
{.uuid = {0xe0, 0x89, 0x86, 0x19, 0xec, 0xcd, 0x43, 0x70, 0x91, 0x41, 0x2c, 0xe1, 0x9b, 0x91, 0xc4, 0x32}, .color_argb = GColorIslamicGreenARGB8}, // Evernote
|
||||
{.uuid = {0xc5, 0xfb, 0xbd, 0x64, 0x9a, 0xae, 0x43, 0x5a, 0xa2, 0x9c, 0x1c, 0x99, 0xf0, 0x72, 0xa8, 0xf3}, .color_argb = GColorBlueMoonARGB8}, // eBay
|
||||
{.uuid = {0x7b, 0x3f, 0x56, 0x49, 0xf0, 0x3a, 0x42, 0xf9, 0x9d, 0x86, 0xbe, 0x21, 0xb0, 0x47, 0x49, 0xa5}, .color_argb = GColorCobaltBlueARGB8}, // Pandora
|
||||
{.uuid = {0x0b, 0x73, 0xb7, 0x6a, 0xcd, 0x65, 0x4d, 0xc2, 0x95, 0x85, 0xaa, 0xa2, 0x13, 0x32, 0x08, 0x58}, .color_argb = GColorBlackARGB8}, // Misfit
|
||||
{.uuid = {0x7f, 0x97, 0x08, 0xf8, 0x9b, 0x15, 0x47, 0x57, 0x99, 0x18, 0xea, 0x3a, 0x4c, 0x85, 0x63, 0xa5}, .color_argb = GColorPictonBlueARGB8}, // Swim.com
|
||||
{.uuid = {0x80, 0x89, 0x03, 0xcb, 0xfa, 0x37, 0x42, 0x47, 0xb0, 0x12, 0x7d, 0xfe, 0x5c, 0x92, 0x69, 0xe1}, .color_argb = GColorOrangeARGB8}, // Pixel Miner
|
||||
{.uuid = {0x2e, 0xdf, 0xf9, 0xdf, 0x4d, 0x60, 0x47, 0xdb, 0x99, 0x62, 0x43, 0x7d, 0x40, 0xc5, 0xf1, 0xe2}, .color_argb = GColorLibertyARGB8}, // Tiny Bird
|
||||
{.uuid = {0x17, 0xb3, 0x32, 0x24, 0x31, 0x7d, 0x44, 0xd7, 0x9e, 0x70, 0x62, 0x80, 0xd4, 0x2f, 0xb9, 0x39}, .color_argb = GColorMidnightGreenARGB8}, // PinyWings
|
||||
{.uuid = {0x43, 0x6f, 0xe4, 0x62, 0x4a, 0xa0, 0x4c, 0xa3, 0xbd, 0x2c, 0x2c, 0x8e, 0x72, 0xb0, 0x32, 0x81}, .color_argb = GColorOrangeARGB8}, // Cards for Pebble
|
||||
{.uuid = {0xdc, 0x36, 0x23, 0xce, 0xc8, 0x04, 0x4a, 0xba, 0x8b, 0xe4, 0xf0, 0x52, 0x73, 0xee, 0x87, 0x74}, .color_argb = GColorDarkGrayARGB8}, // Smartwatch+
|
||||
{.uuid = {0x4b, 0x76, 0x00, 0x64, 0x14, 0x88, 0x40, 0x44, 0x96, 0x7a, 0x1b, 0x1d, 0x3a, 0xb3, 0x05, 0x74}, .color_argb = GColorDarkGrayARGB8}, // Glance
|
||||
{.uuid = {0x24, 0xb0, 0xa1, 0xe5, 0xf0, 0xb1, 0x44, 0x0d, 0x8e, 0x53, 0x8f, 0x26, 0x68, 0x8a, 0x3f, 0x07}, .color_argb = GColorKellyGreenARGB8}, // Sleep as Android
|
||||
{.uuid = {0xe3, 0x06, 0x00, 0x14, 0xbf, 0xe9, 0x4d, 0x03, 0xb7, 0x5e, 0x37, 0x4c, 0xc4, 0xdc, 0xa9, 0x7b}, .color_argb = GColorRajahARGB8}, // Alarms++
|
||||
{.uuid = {0x54, 0x3e, 0xc8, 0xb0, 0xa9, 0x4b, 0x4f, 0xbb, 0x95, 0x31, 0x63, 0x47, 0x84, 0x2f, 0xcd, 0xb1}, .color_argb = GColorVividCeruleanARGB8}, // Timer (3rd party)
|
||||
{.uuid = {0x1b, 0xdf, 0xe4, 0x35, 0x6a, 0x34, 0x42, 0xd5, 0xae, 0xd7, 0xac, 0xe2, 0x9f, 0xec, 0x12, 0x59}, .color_argb = GColorCadetBlueARGB8}, // NavMe
|
||||
{.uuid = {0x4e, 0x81, 0x8f, 0xcd, 0x7d, 0xaf, 0x4d, 0x09, 0xba, 0x51, 0x60, 0x8a, 0x2a, 0x44, 0x98, 0x11}, .color_argb = GColorElectricUltramarineARGB8}, // LetsMuv
|
||||
{.uuid = {0x7e, 0x4b, 0x6a, 0x11, 0xd4, 0x20, 0x46, 0x01, 0xa5, 0xd3, 0xeb, 0x44, 0x09, 0x39, 0x6a, 0x6f}, .color_argb = GColorKellyGreenARGB8}, // Endomondo
|
||||
{.uuid = {0xf3, 0x8e, 0x8e, 0xf7, 0xf2, 0x10, 0x4b, 0x9a, 0xbf, 0xb3, 0x67, 0x26, 0xe2, 0x93, 0x4e, 0x09}, .color_argb = GColorPictonBlueARGB8}, // Movable
|
||||
{.uuid = {0x58, 0x5f, 0x47, 0xed, 0x99, 0x97, 0x4e, 0x32, 0xbd, 0x94, 0x67, 0xb9, 0xc3, 0x64, 0xa5, 0xec}, .color_argb = GColorBlackARGB8}, // Mr Runner - Up
|
||||
{.uuid = {0x09, 0x33, 0x25, 0xba, 0x1b, 0xe9, 0x4f, 0x5f, 0xba, 0x80, 0xb4, 0x75, 0x04, 0x75, 0x41, 0x91}, .color_argb = GColorSunsetOrangeARGB8}, // WW - Weather Watch
|
||||
{.uuid = {0xfb, 0x53, 0x38, 0xd6, 0x75, 0x1c, 0x4d, 0x4f, 0x90, 0x74, 0x70, 0xd4, 0xba, 0xd0, 0x21, 0xa0}, .color_argb = GColorLightGrayARGB8}, // SmartStatus
|
||||
{.uuid = {0x5b, 0xe4, 0x4f, 0x1d, 0xd2, 0x62, 0x4e, 0xa6, 0xaa, 0x30, 0xdd, 0xbe, 0xc1, 0xe3, 0xca, 0xb2}, .color_argb = GColorDukeBlueARGB8}, // Morpheuz
|
||||
{.uuid = {0x5f, 0xac, 0x58, 0x89, 0x26, 0x70, 0x46, 0x43, 0x82, 0x57, 0xb0, 0x73, 0x87, 0x7d, 0x63, 0xce}, .color_argb = GColorIndigoARGB8}, // Awear
|
||||
{.uuid = {0x48, 0x18, 0xa8, 0xbd, 0x6e, 0x53, 0x49, 0x1a, 0xb6, 0xb5, 0x63, 0x02, 0xb3, 0x36, 0x3d, 0x9b}, .color_argb = GColorSpringBudARGB8}, // Battery Lifetime
|
||||
{.uuid = {0xe4, 0x16, 0x8f, 0x6c, 0xb4, 0x85, 0x4b, 0x6f, 0x99, 0xc4, 0x94, 0xaa, 0x95, 0x7e, 0x86, 0xd5}, .color_argb = GColorMalachiteARGB8}, // Music Boss
|
||||
{.uuid = {0x0d, 0xb6, 0xa5, 0x5e, 0xb3, 0x2a, 0x4b, 0x03, 0xb0, 0x37, 0x95, 0x63, 0x7b, 0xf3, 0x06, 0xff}, .color_argb = GColorBlackARGB8}, // MultiTimer
|
||||
{.uuid = {0x4d, 0x6e, 0xa3, 0xee, 0x0f, 0x2a, 0x4c, 0x33, 0xb0, 0x42, 0xe2, 0xe5, 0x6c, 0xe8, 0x0c, 0xd4}, .color_argb = GColorPurpleARGB8}, // Yo
|
||||
{.uuid = {0x93, 0x6a, 0x77, 0x26, 0x4c, 0x8f, 0x41, 0x67, 0x8c, 0x25, 0x46, 0x68, 0x36, 0x82, 0xb0, 0x6e}, .color_argb = GColorDarkGrayARGB8}, // Pebtris
|
||||
{.uuid = {0x22, 0xe5, 0x53, 0x87, 0x92, 0x3b, 0x41, 0x9f, 0xae, 0x03, 0xad, 0x09, 0xff, 0x7e, 0xa2, 0x95}, .color_argb = GColorMelonARGB8}, // Calendar
|
||||
{.uuid = {0x86, 0x5a, 0xd5, 0x5a, 0xe0, 0x18, 0x40, 0x70, 0x97, 0x07, 0x98, 0x22, 0x5b, 0x5a, 0x6a, 0x34}, .color_argb = GColorLightGrayARGB8}, // Asteriod
|
||||
{.uuid = {0x7f, 0xb1, 0xc6, 0x61, 0x04, 0x50, 0x40, 0x92, 0xb4, 0x13, 0x5c, 0xc8, 0x40, 0xfa, 0x09, 0x45}, .color_argb = GColorBlackARGB8}, // 3 Calendar
|
||||
{.uuid = {0x4c, 0x9b, 0x88, 0x5b, 0x40, 0x0d, 0x4f, 0x1c, 0x8e, 0x99, 0x4d, 0x38, 0xa1, 0xb7, 0x96, 0xb4}, .color_argb = GColorBlackARGB8}, // GoPro
|
||||
{.uuid = {0x5c, 0xc7, 0xeb, 0xd1, 0xea, 0x97, 0x49, 0x4c, 0xae, 0x7c, 0xec, 0xd0, 0x5d, 0xd3, 0x3a, 0x5a}, .color_argb = GColorRedARGB8}, // Email to SMS
|
||||
{.uuid = {0x6f, 0x93, 0x02, 0xfc, 0xed, 0x64, 0x43, 0x5a, 0xae, 0x1a, 0x83, 0x30, 0x8f, 0xe1, 0x18, 0x02}, .color_argb = GColorDarkCandyAppleRedARGB8}, // Pebble Snap
|
||||
{.uuid = {0x12, 0x10, 0xb1, 0x65, 0x10, 0x52, 0x44, 0x7d, 0x80, 0xb1, 0x55, 0xce, 0x6e, 0x5d, 0x1b, 0x20}, .color_argb = GColorCobaltBlueARGB8}, // Dominos
|
||||
{.uuid = {0xee, 0x54, 0x11, 0x34, 0x3b, 0x52, 0x4f, 0x7c, 0xbd, 0x88, 0x08, 0xc3, 0x27, 0x6c, 0xa1, 0x4b}, .color_argb = GColorRedARGB8}, // Telepizza
|
||||
{.uuid = {0x5e, 0x6f, 0xa6, 0x42, 0x51, 0xe9, 0x4e, 0xaf, 0xae, 0x07, 0x7c, 0xdc, 0x27, 0x33, 0x0f, 0xf1}, .color_argb = GColorFollyARGB8}, // AirBerlin
|
||||
{.uuid = {0xc9, 0x80, 0x84, 0x49, 0x03, 0x00, 0x4e, 0x33, 0x9a, 0xe4, 0xb1, 0x26, 0x5b, 0xaf, 0x42, 0xab}, .color_argb = GColorDarkGrayARGB8}, // Mercedes Benz
|
||||
{.uuid = {0x58, 0x78, 0x14, 0xd7, 0x79, 0x15, 0x4e, 0x70, 0x8d, 0x77, 0xd5, 0x07, 0x18, 0x7c, 0xb4, 0x4e}, .color_argb = GColorPictonBlueARGB8}, // Gym Timer
|
||||
{.uuid = {0x2c, 0x4a, 0x34, 0x23, 0x2e, 0x3e, 0x46, 0x0a, 0x8f, 0x6d, 0xbc, 0xdb, 0x4b, 0x46, 0x21, 0xb3}, .color_argb = GColorBlueMoonARGB8}, // Bart on Time
|
||||
{.uuid = {0x00, 0xe9, 0xde, 0xeb, 0x16, 0xb4, 0x47, 0x52, 0xae, 0x35, 0x2c, 0xc0, 0x88, 0xfc, 0x6c, 0xa9}, .color_argb = GColorOxfordBlueARGB8}, // UK Transport
|
||||
{.uuid = {0x43, 0xa4, 0xb4, 0xd6, 0x94, 0x04, 0x4f, 0x39, 0x86, 0x8c, 0x59, 0x48, 0x01, 0xa7, 0xb6, 0xb0}, .color_argb = GColorPictonBlueARGB8}, // Twebble
|
||||
{.uuid = {0x7c, 0x62, 0xa8, 0x0d, 0xbf, 0x8f, 0x4c, 0xd0, 0x87, 0x9c, 0x14, 0xb7, 0x2f, 0x41, 0x07, 0x9d}, .color_argb = GColorWindsorTanARGB8}, // JavaPay
|
||||
{.uuid = {0x98, 0xa1, 0xc9, 0x95, 0x74, 0x82, 0x48, 0x61, 0xbf, 0xcf, 0xb4, 0x44, 0x75, 0x6b, 0x77, 0xa3}, .color_argb = GColorLightGrayARGB8}, // PebbDrive
|
||||
{.uuid = {0xf6, 0xf1, 0xa8, 0xdf, 0x4d, 0x06, 0x4e, 0x8a, 0xbe, 0x7c, 0x52, 0xfb, 0x32, 0xf5, 0x3f, 0x98}, .color_argb = GColorRedARGB8}, // PebbleCN
|
||||
{.uuid = {0x03, 0xa8, 0x3a, 0x53, 0x94, 0x33, 0x49, 0xde, 0x89, 0xde, 0xa3, 0x0e, 0xed, 0x2b, 0xd5, 0x89}, .color_argb = GColorRedARGB8}, // PebbleCC
|
||||
{.uuid = {0x1a, 0x1a, 0x0e, 0xa3, 0x15, 0x9b, 0x41, 0x97, 0x94, 0x37, 0x16, 0x24, 0xdd, 0x2f, 0xb0, 0x65}, .color_argb = GColorLimerickARGB8}, // PebbGPS
|
||||
{.uuid = {0x49, 0x82, 0x77, 0x22, 0x4f, 0x3b, 0x4e, 0x3f, 0x9e, 0x96, 0xd6, 0x60, 0x08, 0x9f, 0x50, 0xc1}, .color_argb = GColorMidnightGreenARGB8}, // Compass
|
||||
{.uuid = {0x3a, 0xc7, 0x96, 0xc0, 0x47, 0x5b, 0x4c, 0xdf, 0x99, 0x77, 0x86, 0x8f, 0xfd, 0x5d, 0x22, 0x25}, .color_argb = GColorDarkGrayARGB8}, // Pebblets
|
||||
{.uuid = {0x17, 0xbf, 0x83, 0xaa, 0x88, 0xa9, 0x45, 0x9a, 0xa6, 0xe5, 0x60, 0xaa, 0x60, 0xe1, 0xdc, 0x07}, .color_argb = GColorWindsorTanARGB8}, // Timer+
|
||||
{.uuid = {0xd9, 0x48, 0x17, 0x9c, 0x88, 0xf9, 0x4b, 0x15, 0xa8, 0x4a, 0x35, 0x64, 0x8e, 0x2a, 0x56, 0x10}, .color_argb = GColorVividCeruleanARGB8}, // Leaf
|
||||
{.uuid = {0xd6, 0x2b, 0xc4, 0x67, 0x84, 0x99, 0x4a, 0xb5, 0x8b, 0xf1, 0x35, 0xeb, 0xf1, 0xee, 0x8f, 0x08}, .color_argb = GColorCadetBlueARGB8}, // Wrist Presenter
|
||||
{.uuid = {0xe6, 0x0b, 0x30, 0x32, 0x3d, 0x91, 0x4c, 0xb8, 0x8f, 0xa7, 0x27, 0x49, 0x00, 0x0b, 0x7c, 0xa5}, .color_argb = GColorBlackARGB8}, // Kronos
|
||||
{.uuid = {0x06, 0x0d, 0x81, 0x97, 0x9c, 0xf9, 0x49, 0xd4, 0xab, 0x1d, 0x03, 0x07, 0x4d, 0x0a, 0x6d, 0x50}, .color_argb = GColorOrangeARGB8}, // Grill Timer
|
||||
{.uuid = {0x07, 0xd8, 0x78, 0x11, 0x51, 0x0f, 0x48, 0xf2, 0xb7, 0x23, 0x6b, 0xcf, 0xc4, 0xdb, 0x9a, 0x40}, .color_argb = GColorTiffanyBlueARGB8}, // Pedemeter
|
||||
{.uuid = {0x0f, 0xca, 0x4e, 0x5c, 0xda, 0xf2, 0x4a, 0x9b, 0xa2, 0xbd, 0x53, 0xa7, 0x3f, 0x4b, 0x02, 0x1a}, .color_argb = GColorMayGreenARGB8}, // Swing by Swing
|
||||
{.uuid = {0x73, 0x8f, 0xf8, 0x50, 0xe6, 0x64, 0x44, 0x38, 0x85, 0x34, 0x7f, 0x82, 0x9a, 0x3a, 0x07, 0x1d}, .color_argb = GColorOxfordBlueARGB8}, // Glympse
|
||||
{.uuid = {0x40, 0xdf, 0x22, 0x7e, 0x47, 0x15, 0x43, 0x69, 0x82, 0xca, 0xec, 0x9d, 0xe5, 0x88, 0x89, 0xea}, .color_argb = GColorDarkCandyAppleRedARGB8}, // CalTrain
|
||||
{.uuid = {0x1b, 0x35, 0x6b, 0xed, 0xdb, 0xbc, 0x41, 0xd5, 0xa0, 0xf3, 0xec, 0x60, 0xbd, 0x75, 0x1b, 0x15}, .color_argb = GColorDarkCandyAppleRedARGB8}, // ESPN
|
||||
{.uuid = {0xc6, 0x4a, 0xae, 0x42, 0xb8, 0x22, 0x4d, 0x6d, 0x99, 0x5b, 0xbc, 0x7c, 0x38, 0xe8, 0x43, 0xf4}, .color_argb = GColorCelesteARGB8}, // Timer (1st party)
|
||||
{.uuid = {0x14, 0xbd, 0x9c, 0xd3, 0x14, 0xdc, 0x4b, 0x81, 0xbd, 0x58, 0x2f, 0x1f, 0xb9, 0xb5, 0x79, 0x78}, .color_argb = GColorYellowARGB8}, // Stopwatch
|
31
src/fw/process_management/app_install_manager_private.h
Normal file
31
src/fw/process_management/app_install_manager_private.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "app_install_manager.h"
|
||||
#include "pebble_process_md.h"
|
||||
|
||||
//! @file app_install_manager_private.h
|
||||
//! These are the "private" functions used by submodules of app_install_manager.
|
||||
|
||||
typedef void (*InstallCallbackDoneCallback)(void*);
|
||||
|
||||
//! Used by app_custom_icon and app_db, so invoke add/remove/update/app_db_clear callbacks.
|
||||
//! This function takes care of calling the callbacks on the proper task, so this function
|
||||
//! can be called from any task.
|
||||
//! @return false if a callback is already in progress
|
||||
bool app_install_do_callbacks(InstallEventType event_type, AppInstallId install_id, Uuid *uuid,
|
||||
InstallCallbackDoneCallback done_callback, void* done_callback_data);
|
33
src/fw/process_management/app_install_types.h
Normal file
33
src/fw/process_management/app_install_types.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
//! ID unique to a given app for the duration that it is installed
|
||||
//! System apps (system/resource) and banked applications are negative numbers.
|
||||
//! AppDB flash apps are positive numbers
|
||||
typedef int32_t AppInstallId;
|
||||
|
||||
#define INSTALL_ID_INVALID ((AppInstallId)0)
|
||||
|
||||
//! Returns true for system applications
|
||||
bool app_install_id_from_system(AppInstallId id);
|
||||
|
||||
//! Returns true for user installed applications
|
||||
bool app_install_id_from_app_db(AppInstallId id);
|
960
src/fw/process_management/app_manager.c
Normal file
960
src/fw/process_management/app_manager.c
Normal file
|
@ -0,0 +1,960 @@
|
|||
/*
|
||||
* 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 "app_manager.h"
|
||||
#include "worker_manager.h"
|
||||
#include "process_loader.h"
|
||||
|
||||
// Pebble stuff
|
||||
#include "applib/app_launch_reason.h"
|
||||
#include "applib/app_message/app_message_internal.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/dialogs/dialog.h"
|
||||
#include "applib/ui/dialogs/simple_dialog.h"
|
||||
#include "applib/ui/window_stack.h"
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "console/prompt.h"
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/ui/kernel_ui.h"
|
||||
#include "kernel/ui/modals/modal_manager.h"
|
||||
#include "kernel/util/segment.h"
|
||||
#include "kernel/util/task_init.h"
|
||||
#include "mcu/cache.h"
|
||||
#include "mcu/privilege.h"
|
||||
#include "os/mutex.h"
|
||||
#include "popups/health_tracking_ui.h"
|
||||
#include "popups/timeline/peek.h"
|
||||
#include "process_management/app_run_state.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "process_management/process_heap.h"
|
||||
#include "process_management/sdk_memory_limits.auto.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "resource/resource.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "resource/resource_mapped.h"
|
||||
#include "services/common/analytics/analytics.h"
|
||||
#include "services/common/compositor/compositor_transitions.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/common/light.h"
|
||||
#include "services/normal/app_cache.h"
|
||||
#include "services/normal/app_inbox_service.h"
|
||||
#include "services/normal/app_outbox_service.h"
|
||||
#include "shell/normal/app_idle_timeout.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "shell/shell.h"
|
||||
#include "shell/system_app_state_machine.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
|
||||
// FreeRTOS stuff
|
||||
#include "FreeRTOS.h"
|
||||
#include "freertos_application.h"
|
||||
#include "task.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RETURN_CRASH_TIMEOUT_TICKS (60 * RTC_TICKS_HZ)
|
||||
|
||||
//! Behold! The file that manages applications!
|
||||
//!
|
||||
//! The code in this file applies to all apps, whether they're third party apps (stored in SPI flash) or first
|
||||
//! party apps stored inside our firmware.
|
||||
//!
|
||||
//! Apps are only started and stopped on the launcher task (aka kernel main).
|
||||
|
||||
extern char __APP_RAM__[];
|
||||
extern char __APP_RAM_end__[];
|
||||
extern char __stack_guard_size__[];
|
||||
|
||||
//! Used by the "pebble gdb" command to locate the loaded app in memory.
|
||||
void * volatile g_app_load_address;
|
||||
|
||||
static const int MAX_TO_APP_EVENTS = 32;
|
||||
static QueueHandle_t s_to_app_event_queue;
|
||||
static ProcessContext s_app_task_context;
|
||||
static ProcessAppRunLevel s_minimum_run_level;
|
||||
|
||||
typedef struct NextApp {
|
||||
LaunchConfigCommon common;
|
||||
const PebbleProcessMd *md;
|
||||
WakeupInfo wakeup_info;
|
||||
} NextApp;
|
||||
|
||||
typedef struct {
|
||||
AppInstallId install_id;
|
||||
RtcTicks crash_ticks;
|
||||
} AppCrashInfo;
|
||||
|
||||
static NextApp s_next_app;
|
||||
|
||||
static void prv_handle_app_start_analytics(const PebbleProcessMd *app_md,
|
||||
const AppLaunchReason launch_reason);
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
void app_manager_init(void) {
|
||||
s_to_app_event_queue = xQueueCreate(MAX_TO_APP_EVENTS, sizeof(PebbleEvent));
|
||||
|
||||
s_app_task_context = (ProcessContext) { 0 };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
bool app_manager_is_initialized(void) {
|
||||
return s_to_app_event_queue != NULL;
|
||||
}
|
||||
|
||||
static bool s_first_app_launched = false;
|
||||
bool app_manager_is_first_app_launched(void) {
|
||||
return s_first_app_launched;
|
||||
}
|
||||
|
||||
WakeupInfo app_manager_get_app_wakeup_state(void) {
|
||||
return s_next_app.wakeup_info;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
//! This is the wrapper function for all apps here. It's not allowed to return as it's
|
||||
//! the top frame on the stack created for the application.
|
||||
static void prv_app_task_main(void *entry_point) {
|
||||
app_state_init();
|
||||
task_init();
|
||||
|
||||
// about to start the app in earnest. No longer safe to kill.
|
||||
s_app_task_context.safe_to_kill = false;
|
||||
|
||||
// Enter unprivileged mode!
|
||||
const bool is_unprivileged = s_app_task_context.app_md->is_unprivileged;
|
||||
|
||||
// There are currently no Rocky.js APIs that need to be called while in privileged mode, so run
|
||||
// in unprivileged mode for the built-in Rocky.js apps (Tictoc) as well:
|
||||
const bool is_rocky_app = s_app_task_context.app_md->is_rocky_app;
|
||||
|
||||
if (is_unprivileged || is_rocky_app) {
|
||||
mcu_state_set_thread_privilege(false);
|
||||
}
|
||||
|
||||
const PebbleMain main_func = entry_point;
|
||||
main_func();
|
||||
|
||||
// Clean up after the app. Remember to put only non-critical cleanup here,
|
||||
// as the app may crash or otherwise misbehave. If something really needs to
|
||||
// be cleaned up, make it so the kernel can do it on the apps behalf and put
|
||||
// the call at the bottom of prv_app_cleanup.
|
||||
app_state_deinit();
|
||||
#ifndef RECOVERY_FW
|
||||
app_message_close();
|
||||
#endif
|
||||
|
||||
sys_exit();
|
||||
}
|
||||
|
||||
//! Heap locking function for our app heap. Our process heaps don't actually
|
||||
//! have to be locked because they're the sole property of the process and no
|
||||
//! other tasks should be touching it. All this function does is verify that
|
||||
//! this condition is met before continuing without locking.
|
||||
static void prv_heap_lock(void* unused) {
|
||||
PBL_ASSERT_TASK(PebbleTask_App);
|
||||
}
|
||||
|
||||
void prv_dump_start_app_info(const PebbleProcessMd *app_md) {
|
||||
char *app_type = "";
|
||||
switch (process_metadata_get_app_sdk_type(app_md)) {
|
||||
case ProcessAppSDKType_System:
|
||||
app_type = "system";
|
||||
break;
|
||||
case ProcessAppSDKType_Legacy2x:
|
||||
app_type = "legacy2";
|
||||
break;
|
||||
case ProcessAppSDKType_Legacy3x:
|
||||
app_type = "legacy3";
|
||||
break;
|
||||
case ProcessAppSDKType_4x:
|
||||
app_type = "4.x";
|
||||
break;
|
||||
}
|
||||
|
||||
char *const sdk_platform = platform_type_get_name(process_metadata_get_app_sdk_platform(app_md));
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Starting %s app <%s>", app_type, process_metadata_get_name(app_md));
|
||||
// new logging only allows for 2 %s per format string...
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Starting app with sdk platform %s", sdk_platform);
|
||||
}
|
||||
|
||||
#define APP_STACK_ROCKY_SIZE (8 * 1024)
|
||||
#define APP_STACK_NORMAL_SIZE (2 * 1024)
|
||||
|
||||
static size_t prv_get_app_segment_size(const PebbleProcessMd *app_md) {
|
||||
switch (process_metadata_get_app_sdk_type(app_md)) {
|
||||
case ProcessAppSDKType_Legacy2x:
|
||||
return APP_RAM_2X_SIZE;
|
||||
case ProcessAppSDKType_Legacy3x:
|
||||
return APP_RAM_3X_SIZE;
|
||||
case ProcessAppSDKType_4x:
|
||||
#if CAPABILITY_HAS_JAVASCRIPT
|
||||
if (app_md->is_rocky_app) {
|
||||
// on Spalding, we didn't have enough applib padding to guarantee both,
|
||||
// 4.x native app heap + JerryScript statis + increased stack for Rocky.
|
||||
// For now, we just decrease the amount of available heap as we don't use it.
|
||||
// In the future, we will move the JS stack to the heap PBL-35783,
|
||||
// make byte code swappable PBL-37937,and remove JerryScript's static PBL-40400.
|
||||
// All of the above will work to our advantage so it's safe to make this simple
|
||||
// change now.
|
||||
return APP_RAM_4X_SIZE - (APP_STACK_ROCKY_SIZE - APP_STACK_NORMAL_SIZE);
|
||||
}
|
||||
#endif
|
||||
return APP_RAM_4X_SIZE;
|
||||
case ProcessAppSDKType_System:
|
||||
return APP_RAM_SYSTEM_SIZE;
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t prv_get_app_stack_size(const PebbleProcessMd *app_md) {
|
||||
#if CAPABILITY_HAS_JAVASCRIPT
|
||||
if (app_md->is_rocky_app) {
|
||||
return APP_STACK_ROCKY_SIZE;
|
||||
}
|
||||
#endif
|
||||
return APP_STACK_NORMAL_SIZE;
|
||||
}
|
||||
|
||||
T_STATIC MemorySegment prv_get_app_ram_segment(void) {
|
||||
return (MemorySegment) { __APP_RAM__, __APP_RAM_end__ };
|
||||
}
|
||||
|
||||
T_STATIC size_t prv_get_stack_guard_size(void) {
|
||||
return (uintptr_t)__stack_guard_size__;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
//! @return True on success, False if:
|
||||
//! - We fail to start the app. No app is running and the caller is responsible for starting
|
||||
//! a different app.
|
||||
//!
|
||||
//! @note Side effects: trips assertions if:
|
||||
//! - The app manager was not init,
|
||||
//! - The app's task handle or event queue aren't null
|
||||
//! - The app's metadata is null
|
||||
static bool prv_app_start(const PebbleProcessMd *app_md, const void *args,
|
||||
const AppLaunchReason launch_reason) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
PBL_ASSERTN(app_md);
|
||||
|
||||
prv_dump_start_app_info(app_md);
|
||||
|
||||
process_manager_init_context(&s_app_task_context, app_md, args);
|
||||
|
||||
// Set up the app's memory and load the app into it.
|
||||
size_t app_segment_size = prv_get_app_segment_size(app_md);
|
||||
// The stack guard is counted as part of the app segment size...
|
||||
const size_t stack_guard_size = prv_get_stack_guard_size();
|
||||
// ...and is carved out of the stack.
|
||||
const size_t stack_size = prv_get_app_stack_size(app_md) - stack_guard_size;
|
||||
|
||||
MemorySegment app_ram = prv_get_app_ram_segment();
|
||||
|
||||
#if !UNITTEST
|
||||
if (app_md->is_rocky_app) {
|
||||
/* PBL-40376: Temp hack: put .rocky_bss at end of APP_RAM:
|
||||
Interim solution until all statics are removed from applib & jerry.
|
||||
These statics are only used for rocky apps, so it's OK that this overlaps/overlays with the
|
||||
app heap for non-rocky apps.
|
||||
*/
|
||||
extern char __ROCKY_BSS_size__[];
|
||||
extern char __ROCKY_BSS__[];
|
||||
memset(__ROCKY_BSS__, 0, (size_t)__ROCKY_BSS_size__);
|
||||
|
||||
// ROCKY_BSS is inside APP_RAM to make the syscall buffer checks pass.
|
||||
// However, we want to avoid overlapping with any splits we're about to make:
|
||||
app_ram.end = __ROCKY_BSS__;
|
||||
|
||||
// Reduce the size available for the code + app heap, on Spalding the "padding" we had left
|
||||
// isn't enough to fit Rocky + Jerry's .bss:
|
||||
app_segment_size -= 1400;
|
||||
}
|
||||
#endif
|
||||
|
||||
memset((char *)app_ram.start + stack_guard_size, 0,
|
||||
memory_segment_get_size(&app_ram) - stack_guard_size);
|
||||
|
||||
MemorySegment app_segment;
|
||||
PBL_ASSERTN(memory_segment_split(&app_ram, &app_segment, app_segment_size));
|
||||
PBL_ASSERTN(memory_segment_split(&app_segment, NULL, stack_guard_size));
|
||||
// No (accessible) memory segments can be placed between the top of APP_RAM
|
||||
// and the end of stack. Stacks always grow towards lower memory addresses, so
|
||||
// we want a stack overflow to touch the stack guard region before it begins
|
||||
// to clobber actual data. And syscalls assume that the stack is always at the
|
||||
// top of APP_RAM; violating this assumption will result in syscalls sometimes
|
||||
// failing when the app hasn't done anything wrong.
|
||||
portSTACK_TYPE *stack = memory_segment_split(&app_segment, NULL, stack_size);
|
||||
PBL_ASSERTN(stack);
|
||||
s_app_task_context.load_start = app_segment.start;
|
||||
g_app_load_address = app_segment.start;
|
||||
void *entry_point = process_loader_load(app_md, PebbleTask_App, &app_segment);
|
||||
s_app_task_context.load_end = app_segment.start;
|
||||
if (!entry_point) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Tried to launch an invalid app in bank %u!",
|
||||
process_metadata_get_code_bank_num(app_md));
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResAppNum res_bank_num = process_metadata_get_res_bank_num(app_md);
|
||||
if (res_bank_num != SYSTEM_APP) {
|
||||
const ResourceVersion res_version = process_metadata_get_res_version(app_md);
|
||||
// for RockyJS apps, we initialize without checking the for a match between
|
||||
// binary's copy of the resource CRC and the actual CRC as it could be outdated
|
||||
const ResourceVersion *const res_version_ptr = app_md->is_rocky_app ? NULL : &res_version;
|
||||
if (!resource_init_app(res_bank_num, res_version_ptr)) {
|
||||
// The resources are busted! Abort starting this app.
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR,
|
||||
"Checksum for resources differs or insufficient meta data for JavaScript app.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Synchronously handle process start since its new state is needed for app state initialization
|
||||
timeline_peek_handle_process_start();
|
||||
|
||||
const ProcessAppSDKType sdk_type = process_metadata_get_app_sdk_type(app_md);
|
||||
|
||||
// The rest of app_ram is available for app_state to use as it sees fit.
|
||||
if (!app_state_configure(&app_ram, sdk_type, timeline_peek_get_obstruction_origin_y())) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "App state configuration failed");
|
||||
return false;
|
||||
}
|
||||
// The remaining space in app_segment is assigned to the app's heap.
|
||||
// app_state needs to be configured before initializing the app heap
|
||||
// as the AppState struct holds the app heap's Heap object.
|
||||
|
||||
// Don't fuzz 3rd party app heaps because likely many of them rely on accessing free'd memory
|
||||
bool enable_heap_fuzzing = (sdk_type == ProcessAppSDKType_System);
|
||||
Heap *app_heap = app_state_get_heap();
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "App heap init %p %p",
|
||||
app_segment.start, app_segment.end);
|
||||
heap_init(app_heap, app_segment.start, app_segment.end, enable_heap_fuzzing);
|
||||
heap_set_lock_impl(app_heap, (HeapLockImpl) {
|
||||
.lock_function = prv_heap_lock,
|
||||
});
|
||||
process_heap_set_exception_handlers(app_heap, app_md);
|
||||
|
||||
// We're now going to start the app. We can't abort the app now without calling prv_app_cleanup.
|
||||
|
||||
// If it's a watchface and we were launched by the phone or the user, make it the new default.
|
||||
if ((s_app_task_context.install_id != INSTALL_ID_INVALID) &&
|
||||
((launch_reason == APP_LAUNCH_PHONE) || (launch_reason == APP_LAUNCH_USER))) {
|
||||
AppInstallEntry entry;
|
||||
if (!app_install_get_entry_for_install_id(s_app_task_context.install_id, &entry)) {
|
||||
// cant retrieve app install entry for id
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Failed to get entry for id %"PRId32, s_app_task_context.install_id);
|
||||
return false;
|
||||
}
|
||||
if (app_install_entry_is_watchface(&entry) && !app_install_entry_is_hidden(&entry)) {
|
||||
watchface_set_default_install_id(entry.install_id);
|
||||
}
|
||||
}
|
||||
|
||||
app_manager_set_minimum_run_level(process_metadata_get_run_level(app_md));
|
||||
|
||||
// Use the static app event queue:
|
||||
s_app_task_context.to_process_event_queue = s_to_app_event_queue;
|
||||
|
||||
// Init services required for this process before it starts to execute
|
||||
process_manager_process_setup(PebbleTask_App);
|
||||
|
||||
char task_name[configMAX_TASK_NAME_LEN];
|
||||
snprintf(task_name, sizeof(task_name), "App <%s>", process_metadata_get_name(s_app_task_context.app_md));
|
||||
|
||||
TaskParameters_t task_params = {
|
||||
.pvTaskCode = prv_app_task_main,
|
||||
.pcName = task_name,
|
||||
.usStackDepth = stack_size / sizeof(portSTACK_TYPE),
|
||||
.pvParameters = entry_point,
|
||||
.uxPriority = APP_TASK_PRIORITY | portPRIVILEGE_BIT,
|
||||
.puxStackBuffer = stack,
|
||||
};
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Starting %s", task_name);
|
||||
|
||||
// Store slot of launched app for reboot support (flash apps only)
|
||||
reboot_set_slot_of_last_launched_app(
|
||||
(app_md->process_storage == ProcessStorageFlash) ?
|
||||
process_metadata_get_code_bank_num(app_md) : SYSTEM_APP_BANK_ID);
|
||||
|
||||
pebble_task_create(PebbleTask_App, &task_params, &s_app_task_context.task_handle);
|
||||
|
||||
// Always notify the phone that the application is running
|
||||
app_run_state_send_update(&app_md->uuid, RUNNING);
|
||||
|
||||
system_app_state_machine_register_app_launch(s_app_task_context.install_id);
|
||||
|
||||
prv_handle_app_start_analytics(app_md, launch_reason);
|
||||
|
||||
#if CAPABILITY_HAS_HEALTH_TRACKING && !defined(RECOVERY_FW)
|
||||
health_tracking_ui_register_app_launch(s_app_task_context.install_id);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
//! Kills the app, giving it no chance to clean things up or exit gracefully. The app must
|
||||
//! already be in a state where it's safe to exit.
|
||||
//! Note that the app may not have ever been successfully started when this is called, so check
|
||||
//! your null pointers!
|
||||
static void prv_app_cleanup(void) {
|
||||
// Back button may have been held down when this app quits.
|
||||
launcher_cancel_force_quit();
|
||||
|
||||
// Always notify the phone that the application is not running
|
||||
app_run_state_send_update(&s_app_task_context.app_md->uuid, NOT_RUNNING);
|
||||
|
||||
// Perform generic process cleanup. Note that s_app_task_context will be cleaned up and zero'd
|
||||
// by this.
|
||||
process_manager_process_cleanup(PebbleTask_App);
|
||||
|
||||
// Perform app specific cleanup
|
||||
app_idle_timeout_stop();
|
||||
#ifndef RECOVERY_FW
|
||||
app_inbox_service_unregister_all();
|
||||
app_outbox_service_cleanup_all_pending_messages();
|
||||
#endif
|
||||
light_reset_user_controlled();
|
||||
sys_vibe_history_stop_collecting();
|
||||
#if !defined(PLATFORM_TINTIN)
|
||||
ble_app_cleanup();
|
||||
#endif
|
||||
#if CAPABILITY_HAS_MAPPABLE_FLASH
|
||||
resource_mapped_release_all(PebbleTask_App);
|
||||
#endif
|
||||
|
||||
app_comm_set_sniff_interval(SNIFF_INTERVAL_NORMAL);
|
||||
|
||||
app_manager_set_minimum_run_level(ProcessAppRunLevelNormal);
|
||||
app_install_cleanup_registered_app_callbacks();
|
||||
app_install_notify_app_closed();
|
||||
|
||||
timeline_peek_handle_process_kill();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
//! On watchface crashes, we want to signal to the user that the watchface has crashed so that
|
||||
//! they understand why are being jettisoned into the launcher.
|
||||
static void prv_app_show_crash_ui(AppInstallId install_id) {
|
||||
AppInstallEntry entry;
|
||||
|
||||
if (!app_install_get_entry_for_install_id(install_id, &entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!app_install_entry_is_watchface(&entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(RECOVERY_FW)
|
||||
static AppCrashInfo crash_info = { 0 };
|
||||
// If the same watchface crashes twice in one minute, then we show a dialog informing
|
||||
// the user that the watchface has crashed. Any button press will dismiss
|
||||
// the dialog and show us the default system watch face.
|
||||
PBL_ASSERTN(install_id != INSTALL_ID_INVALID);
|
||||
if (crash_info.install_id != install_id ||
|
||||
(crash_info.crash_ticks + RETURN_CRASH_TIMEOUT_TICKS) < rtc_get_ticks()) {
|
||||
crash_info = (AppCrashInfo) {
|
||||
.install_id = install_id,
|
||||
.crash_ticks = rtc_get_ticks()
|
||||
};
|
||||
// Re-launch immediately
|
||||
watchface_launch_default(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleDialog *crash_dialog = simple_dialog_create("Watchface crashed");
|
||||
Dialog *dialog = simple_dialog_get_dialog(crash_dialog);
|
||||
const char *text_fmt = i18n_get("%.*s is not responding", crash_dialog);
|
||||
unsigned int name_len = 15;
|
||||
char text[DIALOG_MAX_MESSAGE_LEN];
|
||||
sniprintf(text, DIALOG_MAX_MESSAGE_LEN, text_fmt, name_len, entry.name);
|
||||
|
||||
dialog_set_text(dialog, text);
|
||||
dialog_set_icon(dialog, RESOURCE_ID_GENERIC_WARNING_LARGE);
|
||||
dialog_set_timeout(dialog, DIALOG_TIMEOUT_INFINITE /* no timeout */);
|
||||
|
||||
// Any sort of application crash or window crash is a critical message as it
|
||||
// impacts the UX experience, so we want to push it to the forefront of the
|
||||
// window stack.
|
||||
WindowStack *window_stack = modal_manager_get_window_stack(ModalPriorityAlert);
|
||||
simple_dialog_push(crash_dialog, window_stack);
|
||||
|
||||
#if PBL_ROUND
|
||||
// For circular display, reduce app name length until message fits on the screen
|
||||
// This has to occur after the dialog window load has been called to provide
|
||||
// initial layout, text_layer flow and text_layer positions
|
||||
TextLayer *text_layer = &dialog->text_layer;
|
||||
const unsigned int min_text_len = 3;
|
||||
const int max_text_height = 2 * fonts_get_font_height(text_layer->font) + 8;
|
||||
GContext *ctx = graphics_context_get_current_context();
|
||||
int32_t text_height = text_layer_get_content_size(ctx, text_layer).h;
|
||||
|
||||
// Until the text_height fits max_text_height or the app name is min_text_len
|
||||
while (text_height > max_text_height && name_len > min_text_len) {
|
||||
name_len--;
|
||||
sniprintf(text, DIALOG_MAX_MESSAGE_LEN, text_fmt, name_len, entry.name);
|
||||
dialog_set_text(dialog, text);
|
||||
text_height = text_layer_get_content_size(ctx, text_layer).h;
|
||||
}
|
||||
#endif
|
||||
|
||||
i18n_free_all(crash_dialog);
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Watchface crashed, launching default.");
|
||||
|
||||
crash_info = (AppCrashInfo) { 0 };
|
||||
|
||||
watchface_set_default_install_id(INSTALL_ID_INVALID);
|
||||
watchface_launch_default(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
//! Switch to the app stored in the s_next_app global. The gracefully flag tells us whether to attempt a graceful
|
||||
//! exit or not.
|
||||
//!
|
||||
//! For a graceful exit, if the app has not alreeady finished it's de-init, we post a de_init event to the app, set
|
||||
//! a 3 second timer, and return immediately to the caller. If/when the app finally finishes deinit, it will post a
|
||||
//! PEBBLE_PROCESS_KILL_EVENT (graceful=true), which results in this method being again with graceful=true. We will then
|
||||
//! see that the de_init already finished in that second invocation.
|
||||
//!
|
||||
//! If the app has finished its de-init, or graceful is false, we proceed to kill the app task and launch the next
|
||||
//! app as stored in the s_next_app global.
|
||||
//!
|
||||
//! Returns true if new app was just switched in.
|
||||
static bool prv_app_switch(bool gracefully) {
|
||||
ProcessContext *app_task_ctx = &s_app_task_context;
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Switching from '%s' to '%s', graceful=%d...",
|
||||
process_metadata_get_name(app_task_ctx->app_md),
|
||||
process_metadata_get_name(s_next_app.md),
|
||||
(int)gracefully);
|
||||
|
||||
// Shouldn't be called from app. Use app_manager_put_kill_app_event() instead.
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
|
||||
// We have to call this here, in addition to calling it in prv_app_cleanup(),
|
||||
// because the timer could otherwise be triggered while waiting for the task
|
||||
// to exit, causing the app we land on to be killed when it shouldn't be.
|
||||
launcher_cancel_force_quit();
|
||||
|
||||
// Make sure the process is safe to kill. If this method returns false, it will have set a timer to post
|
||||
// another KILL event in a few seconds, thus giving the process a chance to clean up.
|
||||
if (!process_manager_make_process_safe_to_kill(PebbleTask_App, gracefully)) {
|
||||
// Maybe next time...
|
||||
return false;
|
||||
}
|
||||
|
||||
AppInstallId old_install_id = s_app_task_context.install_id;
|
||||
|
||||
// Kill the current app
|
||||
prv_app_cleanup();
|
||||
|
||||
// If we had to ungracefully kill the current app, switch to the launcher app
|
||||
if (!gracefully) {
|
||||
app_install_release_md(s_next_app.md);
|
||||
s_next_app = (NextApp) {
|
||||
.md = system_app_state_machine_get_default_app(),
|
||||
};
|
||||
} else {
|
||||
// Get the next app to launch
|
||||
if (!s_next_app.md) {
|
||||
// There is no next app to launch? We're starting up, let's launch the startup app.
|
||||
app_install_release_md(s_next_app.md);
|
||||
s_next_app = (NextApp) {
|
||||
.md = system_app_state_machine_system_start(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Launch the new app
|
||||
if (!prv_app_start(s_next_app.md, s_next_app.common.args, s_next_app.common.reason)) {
|
||||
if (s_next_app.md->process_storage != ProcessStorageFlash) {
|
||||
PBL_CROAK("Failed to start system app <%s>!", process_metadata_get_name(s_next_app.md));
|
||||
}
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Failed to start app <%s>! Restarting launcher",
|
||||
process_metadata_get_name(s_next_app.md));
|
||||
|
||||
prv_app_start(system_app_state_machine_system_start(), NULL, APP_LAUNCH_SYSTEM);
|
||||
}
|
||||
|
||||
compositor_transition(s_next_app.common.transition);
|
||||
|
||||
// Check if we've exited gracefully. Otherwise, display the crash dialog if appropriate.
|
||||
if (!gracefully) {
|
||||
prv_app_show_crash_ui(old_install_id);
|
||||
}
|
||||
|
||||
// Clear for next time.
|
||||
s_next_app = (NextApp) {};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
void app_manager_start_first_app(void) {
|
||||
const PebbleProcessMd* app_md = system_app_state_machine_system_start();
|
||||
PBL_ASSERTN(prv_app_start(app_md, 0, APP_LAUNCH_SYSTEM));
|
||||
s_first_app_launched = true;
|
||||
compositor_transition(NULL);
|
||||
}
|
||||
|
||||
static const CompositorTransition *prv_get_transition(const LaunchConfigCommon *config,
|
||||
AppInstallId new_app_id) {
|
||||
return config->transition ?: shell_get_open_compositor_animation(s_app_task_context.install_id,
|
||||
new_app_id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
void app_manager_put_launch_app_event(const AppLaunchEventConfig *config) {
|
||||
PBL_ASSERTN(config->id != INSTALL_ID_INVALID);
|
||||
|
||||
PebbleLaunchAppEventExtended *data = kernel_malloc_check(sizeof(PebbleLaunchAppEventExtended));
|
||||
*data = (PebbleLaunchAppEventExtended) {
|
||||
.common = config->common
|
||||
};
|
||||
data->common.transition = prv_get_transition(&config->common, config->id);
|
||||
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_APP_LAUNCH_EVENT,
|
||||
.launch_app = {
|
||||
.id = config->id,
|
||||
.data = data
|
||||
},
|
||||
};
|
||||
|
||||
event_put(&e);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
bool app_manager_launch_new_app(const AppLaunchConfig *config) {
|
||||
// Note that config has a dynamically allocated member that needs to be free'd with
|
||||
// app_install_release_md if we don't actually proceed with launching the app.
|
||||
|
||||
const PebbleProcessMd *app_md = config->md;
|
||||
const AppInstallId new_app_id = app_install_get_id_for_uuid(&app_md->uuid);
|
||||
|
||||
if (!config->restart && uuid_equal(&(app_md->uuid), &(s_app_task_context.app_md->uuid))) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Ignoring launch for app <%s>, app is already running",
|
||||
process_metadata_get_name(app_md));
|
||||
|
||||
app_install_release_md(app_md);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (process_metadata_get_run_level(app_md) < s_minimum_run_level) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING,
|
||||
"Ignoring launch for app <%s>, minimum run level %d, app run level %d",
|
||||
process_metadata_get_name(app_md), s_minimum_run_level,
|
||||
process_metadata_get_run_level(app_md));
|
||||
|
||||
app_install_release_md(app_md);
|
||||
return false;
|
||||
}
|
||||
|
||||
s_next_app = (NextApp) {
|
||||
.md = app_md,
|
||||
.common = config->common,
|
||||
};
|
||||
s_next_app.common.transition = prv_get_transition(&config->common, new_app_id);
|
||||
|
||||
if ((config->common.reason == APP_LAUNCH_WAKEUP) && (config->common.args != NULL)) {
|
||||
WakeupInfo *wakeup_info = (WakeupInfo *)config->common.args;
|
||||
s_next_app.wakeup_info = *(WakeupInfo *)wakeup_info;
|
||||
|
||||
// Stop pointing at the old storage location for wakeup_info so we don't keep the dangling
|
||||
// pointer around.
|
||||
s_next_app.common.args = NULL;
|
||||
}
|
||||
|
||||
return prv_app_switch(!config->forcefully);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
void app_manager_handle_app_fetch_request_event(const PebbleAppFetchRequestEvent *const evt) {
|
||||
PBL_ASSERTN(evt);
|
||||
if (!evt->with_ui) {
|
||||
return;
|
||||
}
|
||||
const AppFetchUIArgs *const fetch_args = evt->fetch_args;
|
||||
app_manager_launch_new_app(&(AppLaunchConfig) {
|
||||
.md = app_fetch_ui_get_app_info(),
|
||||
.common.args = fetch_args,
|
||||
.common.transition = fetch_args->common.transition,
|
||||
.forcefully = fetch_args->forcefully,
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
static AppInstallId prv_get_app_exit_reason_destination_install_id_override(void) {
|
||||
switch (s_app_task_context.exit_reason) {
|
||||
case APP_EXIT_NOT_SPECIFIED:
|
||||
return INSTALL_ID_INVALID;
|
||||
case APP_EXIT_ACTION_PERFORMED_SUCCESSFULLY:
|
||||
PBL_LOG(LOG_LEVEL_INFO,
|
||||
"Next app overridden with watchface because action was performed successfully");
|
||||
return watchface_get_default_install_id();
|
||||
// Handling this case specifically instead of providing a default case ensures that the addition
|
||||
// of future exit reason values will cause compilation to fail until the new case is handled
|
||||
case NUM_EXIT_REASONS:
|
||||
break;
|
||||
}
|
||||
WTF;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void app_manager_close_current_app(bool gracefully) {
|
||||
// This method can be called as a result of receiving a PEBBLE_PROCESS_KILL_EVENT notification
|
||||
// from an app, telling us that it just finished it's deinit. Don't replace s_next_app.md if
|
||||
// perhaps it was already set by someone who called app_manager_launch_new_app or
|
||||
// app_manager_launch_new_app_with_args and asked the current app to exit.
|
||||
const AppInstallId current_app_id = s_app_task_context.install_id;
|
||||
AppInstallId destination_app_id = INSTALL_ID_INVALID;
|
||||
|
||||
#if !RECOVERY_FW
|
||||
destination_app_id = prv_get_app_exit_reason_destination_install_id_override();
|
||||
#endif
|
||||
|
||||
if (destination_app_id == INSTALL_ID_INVALID) {
|
||||
// If we get here, the app exit reason didn't override the destination app ID
|
||||
if (!s_next_app.md) {
|
||||
destination_app_id = system_app_state_machine_get_last_registered_app();
|
||||
} else {
|
||||
// If we get here, s_next_app is already setup and so we can call prv_app_switch() directly
|
||||
// and return
|
||||
prv_app_switch(gracefully);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
app_manager_set_minimum_run_level(ProcessAppRunLevelNormal);
|
||||
process_manager_launch_process(&(ProcessLaunchConfig) {
|
||||
.id = destination_app_id,
|
||||
.common.transition = shell_get_close_compositor_animation(current_app_id, destination_app_id),
|
||||
.forcefully = !gracefully,
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void app_manager_set_minimum_run_level(ProcessAppRunLevel run_level) {
|
||||
s_minimum_run_level = run_level;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------
|
||||
void app_manager_force_quit_to_launcher(void) {
|
||||
const PebbleProcessMd *default_process = system_app_state_machine_get_default_app();
|
||||
const AppInstallId current_app_id = s_app_task_context.install_id;
|
||||
const AppInstallId new_app_id = app_install_get_id_for_uuid(&default_process->uuid);
|
||||
s_next_app = (NextApp) {
|
||||
.md = default_process,
|
||||
};
|
||||
s_next_app.common.transition = shell_get_close_compositor_animation(current_app_id, new_app_id);
|
||||
|
||||
prv_app_switch(true /*gracefully*/);
|
||||
}
|
||||
|
||||
const PebbleProcessMd* app_manager_get_current_app_md(void) {
|
||||
return s_app_task_context.app_md;
|
||||
}
|
||||
|
||||
AppInstallId app_manager_get_current_app_id(void) {
|
||||
return s_app_task_context.install_id;
|
||||
}
|
||||
|
||||
ProcessContext* app_manager_get_task_context(void) {
|
||||
return &s_app_task_context;
|
||||
}
|
||||
|
||||
bool app_manager_is_watchface_running(void) {
|
||||
return (app_manager_get_current_app_md()->process_type == ProcessTypeWatchface);
|
||||
}
|
||||
|
||||
ResAppNum app_manager_get_current_resource_num(void) {
|
||||
return process_metadata_get_res_bank_num(s_app_task_context.app_md);
|
||||
}
|
||||
|
||||
AppLaunchReason app_manager_get_launch_reason(void) {
|
||||
return s_next_app.common.reason;
|
||||
}
|
||||
|
||||
ButtonId app_manager_get_launch_button(void) {
|
||||
return s_next_app.common.button;
|
||||
}
|
||||
|
||||
void app_manager_get_framebuffer_size(GSize *size) {
|
||||
if (size == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_app_task_context.app_md) {
|
||||
// No app has been started yet, so just use the default system size
|
||||
*size = GSize(DISP_COLS, DISP_ROWS);
|
||||
return;
|
||||
}
|
||||
|
||||
// Platform matches current platform
|
||||
const PlatformType sdk_platform =
|
||||
process_metadata_get_app_sdk_platform(s_app_task_context.app_md);
|
||||
|
||||
if (sdk_platform == PBL_PLATFORM_TYPE_CURRENT) {
|
||||
*size = GSize(DISP_COLS, DISP_ROWS);
|
||||
return;
|
||||
}
|
||||
|
||||
// We cannot use the SDK type for this compatibility check but there's
|
||||
// also no easy way to get the resolutions per platform.
|
||||
// so we re-use the suboptimal defines from each display_<model>.h
|
||||
switch (sdk_platform) {
|
||||
case PlatformTypeAplite:
|
||||
*size = GSize(LEGACY_2X_DISP_COLS, LEGACY_2X_DISP_ROWS);
|
||||
return;
|
||||
case PlatformTypeBasalt:
|
||||
case PlatformTypeChalk:
|
||||
// yes, this is misleading, e.g. on Spalding, these defines are always 180x180
|
||||
// oh dear...
|
||||
*size = GSize(LEGACY_3X_DISP_COLS, LEGACY_3X_DISP_ROWS);
|
||||
return;
|
||||
case PlatformTypeDiorite:
|
||||
case PlatformTypeEmery:
|
||||
*size = GSize(DISP_COLS, DISP_ROWS);
|
||||
return;
|
||||
}
|
||||
WTF;
|
||||
}
|
||||
|
||||
bool app_manager_is_app_supported(const PebbleProcessMd *md) {
|
||||
// Get the app ram size depending on the SDK type.
|
||||
// Unsupported SDK types will have a size of 0.
|
||||
return prv_get_app_segment_size(md) > 0;
|
||||
}
|
||||
|
||||
|
||||
// Commands
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
void command_get_active_app_metadata(void) {
|
||||
char buffer[32];
|
||||
|
||||
const PebbleProcessMd* app_metadata = app_manager_get_current_app_md();
|
||||
if (app_metadata != NULL) {
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "app name: %s",
|
||||
process_metadata_get_name(app_metadata));
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "is watchface: %d",
|
||||
(app_metadata->process_type == ProcessTypeWatchface));
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "visibility: %u", app_metadata->visibility);
|
||||
prompt_send_response_fmt(buffer, sizeof(buffer), "bank: %d",
|
||||
(uint8_t) process_metadata_get_res_bank_num(app_metadata));
|
||||
} else {
|
||||
prompt_send_response("metadata lookup failed: no app running");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Analytics
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
static void prv_handle_app_start_analytics(const PebbleProcessMd *app_md,
|
||||
const AppLaunchReason launch_reason) {
|
||||
analytics_event_app_launch(&app_md->uuid);
|
||||
analytics_inc(ANALYTICS_APP_METRIC_LAUNCH_COUNT, AnalyticsClient_App);
|
||||
analytics_stopwatch_start(ANALYTICS_APP_METRIC_FRONT_MOST_TIME, AnalyticsClient_App);
|
||||
|
||||
Version app_sdk_version = process_metadata_get_sdk_version(app_md);
|
||||
analytics_set(ANALYTICS_APP_METRIC_SDK_MAJOR_VERSION, app_sdk_version.major, AnalyticsClient_App);
|
||||
analytics_set(ANALYTICS_APP_METRIC_SDK_MINOR_VERSION, app_sdk_version.minor, AnalyticsClient_App);
|
||||
|
||||
Version app_version = process_metadata_get_process_version(app_md);
|
||||
analytics_set(ANALYTICS_APP_METRIC_APP_MAJOR_VERSION, app_version.major, AnalyticsClient_App);
|
||||
analytics_set(ANALYTICS_APP_METRIC_APP_MINOR_VERSION, app_version.minor, AnalyticsClient_App);
|
||||
|
||||
ResourceVersion resource_version = process_metadata_get_res_version(app_md);
|
||||
analytics_set(ANALYTICS_APP_METRIC_RESOURCE_TIMESTAMP, resource_version.timestamp, AnalyticsClient_App);
|
||||
|
||||
if (app_md->is_rocky_app) {
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_APP_ROCKY_LAUNCH_COUNT, AnalyticsClient_System);
|
||||
analytics_inc(ANALYTICS_APP_METRIC_ROCKY_LAUNCH_COUNT, AnalyticsClient_App);
|
||||
}
|
||||
|
||||
if (launch_reason == APP_LAUNCH_QUICK_LAUNCH) {
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_APP_QUICK_LAUNCH_COUNT, AnalyticsClient_System);
|
||||
analytics_inc(ANALYTICS_APP_METRIC_QUICK_LAUNCH_COUNT, AnalyticsClient_App);
|
||||
} else if (launch_reason == APP_LAUNCH_USER) {
|
||||
analytics_inc(ANALYTICS_DEVICE_METRIC_APP_USER_LAUNCH_COUNT, AnalyticsClient_System);
|
||||
analytics_inc(ANALYTICS_APP_METRIC_USER_LAUNCH_COUNT, AnalyticsClient_App);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------------------------------------------
|
||||
/*!
|
||||
@brief User mode access to its UUID.
|
||||
@param[out] uuid The app's UUID.
|
||||
*/
|
||||
DEFINE_SYSCALL(void, sys_get_app_uuid, Uuid *uuid) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(uuid, sizeof(*uuid));
|
||||
}
|
||||
|
||||
*uuid = app_manager_get_current_app_md()->uuid;
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(Version, sys_get_current_app_sdk_version, void) {
|
||||
return process_metadata_get_sdk_version(app_manager_get_current_app_md());
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(bool, sys_get_current_app_is_js_allowed, void) {
|
||||
return (app_manager_get_current_app_md()->allow_js);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(bool, sys_get_current_app_is_rocky_app, void) {
|
||||
return (app_manager_get_current_app_md()->is_rocky_app);
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(PlatformType, sys_get_current_app_sdk_platform, void) {
|
||||
return process_metadata_get_app_sdk_platform(app_manager_get_current_app_md());
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(bool, sys_app_is_watchface, void) {
|
||||
return app_manager_is_watchface_running();
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(ResAppNum, sys_get_current_resource_num, void) {
|
||||
|
||||
if (pebble_task_get_current() == PebbleTask_KernelMain) {
|
||||
return SYSTEM_APP;
|
||||
}
|
||||
|
||||
return process_metadata_get_res_bank_num(app_manager_get_current_app_md());
|
||||
}
|
||||
|
||||
DEFINE_SYSCALL(AppInstallId, sys_app_manager_get_current_app_id, void) {
|
||||
return app_manager_get_current_app_id();
|
||||
}
|
121
src/fw/process_management/app_manager.h
Normal file
121
src/fw/process_management/app_manager.h
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "launch_config.h"
|
||||
#include "process_manager.h"
|
||||
|
||||
#include "kernel/events.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "resource/resource.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#define APP_TASK_PRIORITY (tskIDLE_PRIORITY + 2)
|
||||
|
||||
typedef enum {
|
||||
AppTaskCtxIdxLauncher = 0,
|
||||
AppTaskCtxIdxApp,
|
||||
AppTaskCtxIdxCount,
|
||||
AppTaskCtxIdxInvalid = -1,
|
||||
} AppTaskCtxIdx;
|
||||
|
||||
typedef struct AppLaunchConfig {
|
||||
LaunchConfigCommon common;
|
||||
const PebbleProcessMd *md;
|
||||
bool restart; //!< Allows the current app to be restarted
|
||||
bool forcefully; //!< Causes the current app to be forcefully closed
|
||||
} AppLaunchConfig;
|
||||
|
||||
typedef struct AppLaunchEventConfig {
|
||||
LaunchConfigCommon common;
|
||||
AppInstallId id;
|
||||
} AppLaunchEventConfig;
|
||||
|
||||
|
||||
// App management functions
|
||||
void app_manager_init(void);
|
||||
|
||||
bool app_manager_is_initialized(void);
|
||||
|
||||
void app_manager_start_first_app(void);
|
||||
|
||||
bool app_manager_is_first_app_launched(void);
|
||||
|
||||
|
||||
//! Start up a new application with the given metadata. This will kill the currently running app.
|
||||
//! May only be called from the KernelMain task
|
||||
bool app_manager_launch_new_app(const AppLaunchConfig *config);
|
||||
|
||||
//! Handles the PebbleEvent of type PEBBLE_APP_FETCH_REQUEST_EVENT, for example, by launching the
|
||||
//! fetched app.
|
||||
void app_manager_handle_app_fetch_request_event(const PebbleAppFetchRequestEvent *const evt);
|
||||
|
||||
//! Close the currently running app. This will cause the next app in the
|
||||
//! system app state machine to automatically get launched.
|
||||
//! May only be called from the KernelMain task
|
||||
void app_manager_close_current_app(bool gracefully);
|
||||
|
||||
//! Stop the current app, and bring up the launcher.
|
||||
void app_manager_force_quit_to_launcher(void);
|
||||
|
||||
//! Sets a minimum run level for app launches that interrupt the current running app. Any app below the specified
|
||||
//! will be ignored when trying to launch.
|
||||
//! The minimum run level will be reset to the incoming app whenever an app is launched. This function just allows
|
||||
//! an app to modify the minimum level between app changes.
|
||||
void app_manager_set_minimum_run_level(ProcessAppRunLevel run_level);
|
||||
|
||||
//! Gets the wakeup info from the PebbleEvent sent to app_manager
|
||||
//! to provide newly launched app access to AppLaunchEvent data
|
||||
WakeupInfo app_manager_get_app_wakeup_state(void);
|
||||
|
||||
//! @return Whether or not we support an app execution environment for this app.
|
||||
bool app_manager_is_app_supported(const PebbleProcessMd *md);
|
||||
|
||||
// Send App Management Events To The Launcher
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//! Adds a PebbleLaunchAppEvent to the event queue
|
||||
void app_manager_put_launch_app_event(const AppLaunchEventConfig *config);
|
||||
|
||||
// Getters For App Management State
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const PebbleProcessMd* app_manager_get_current_app_md(void);
|
||||
|
||||
AppInstallId app_manager_get_current_app_id(void);
|
||||
|
||||
const PebbleProcessMd* app_manager_get_current_worker(void);
|
||||
|
||||
ProcessContext* app_manager_get_task_context(void);
|
||||
|
||||
bool app_manager_is_watchface_running(void);
|
||||
|
||||
ResAppNum app_manager_get_current_resource_num(void);
|
||||
|
||||
AppLaunchReason app_manager_get_launch_reason(void);
|
||||
|
||||
ButtonId app_manager_get_launch_button(void);
|
||||
|
||||
void app_manager_get_framebuffer_size(GSize *size);
|
||||
|
||||
|
||||
//! Exit the application. Do some cleanup to make sure things close nicely.
|
||||
//! Called from the app task
|
||||
NORETURN app_task_exit(void);
|
||||
|
544
src/fw/process_management/app_menu_data_source.c
Normal file
544
src/fw/process_management/app_menu_data_source.c
Normal file
|
@ -0,0 +1,544 @@
|
|||
/*
|
||||
* 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 "app_menu_data_source.h"
|
||||
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/menu_layer.h"
|
||||
#include "apps/system_app_ids.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "resource/resource_ids.auto.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/normal/process_management/app_order_storage.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "apps/system_apps/activity_demo_app.h"
|
||||
#include "shell/prefs.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static void add_app_with_install_id(const AppInstallEntry *entry, AppMenuDataSource *source);
|
||||
static bool remove_app_with_install_id(const AppInstallId install_id, AppMenuDataSource *source);
|
||||
static AppMenuNode * prv_find_node_with_install_id(const AppInstallId install_id,
|
||||
const AppMenuDataSource * const source);
|
||||
static void prv_unload_node(const AppMenuDataSource *source, AppMenuNode *node);
|
||||
|
||||
////////////////////////////////
|
||||
// List helper functions
|
||||
////////////////////////////////
|
||||
|
||||
static bool prv_is_app_filtered_out(AppInstallEntry *entry, AppMenuDataSource * const source) {
|
||||
return (source->callbacks.filter && (source->callbacks.filter(source, entry) == false));
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Order List helpers
|
||||
/////////////////////////
|
||||
|
||||
//! Place these in the order that is desired in the Launcher.
|
||||
//! Set `move_on_activity` to true if you only want the item to jump to the top during communication
|
||||
//! The movement will not happen while looking at the launcher, it will only refresh on a
|
||||
//! close->open
|
||||
static const struct {
|
||||
AppInstallId install_id;
|
||||
bool move_on_activity;
|
||||
} s_override_table[] = {
|
||||
{ APP_ID_SPORTS, false },
|
||||
{ APP_ID_GOLF, false },
|
||||
#ifdef APP_ID_WORKOUT
|
||||
{ APP_ID_WORKOUT, true },
|
||||
#endif
|
||||
{ APP_ID_MUSIC, true },
|
||||
};
|
||||
|
||||
// Returns 0 if not in table. Otherwise, returns the rank in `s_override_table`. Rank is
|
||||
// where the lowest index returns the highest rank
|
||||
static int prv_override_index(AppInstallId app_id) {
|
||||
const uint32_t num_overrides = ARRAY_LENGTH(s_override_table);
|
||||
for (uint32_t i = 0; i < num_overrides; i++) {
|
||||
AppInstallId cur_id = s_override_table[i].install_id;
|
||||
if (cur_id == app_id) {
|
||||
const bool should_move = (!s_override_table[i].move_on_activity ||
|
||||
app_install_is_prioritized(cur_id));
|
||||
return (should_move) ? (num_overrides - i + 1) : 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int prv_app_override_comparator(AppInstallId app_id, AppInstallId new_id) {
|
||||
return (prv_override_index(app_id) - prv_override_index(new_id));
|
||||
}
|
||||
|
||||
static int prv_comparator_ascending_zero_last(unsigned int a, unsigned int b) {
|
||||
return ((a != 0) && (b != 0)) ? (b - a) : // Sort in ascending order
|
||||
(a - b); // 0 should be sorted last so invert the sort
|
||||
}
|
||||
|
||||
T_STATIC int prv_app_node_comparator(void *app_node_ref, void *new_node_ref) {
|
||||
const AppMenuNode *app_node = app_node_ref;
|
||||
const AppMenuNode *new_node = new_node_ref;
|
||||
|
||||
const bool is_app_quick_launch = (app_node->visibility == ProcessVisibilityQuickLaunch);
|
||||
const bool is_new_quick_launch = (new_node->visibility == ProcessVisibilityQuickLaunch);
|
||||
const int override_cmp_rv = prv_app_override_comparator(app_node->install_id,
|
||||
new_node->install_id);
|
||||
if (is_app_quick_launch != is_new_quick_launch) {
|
||||
// Quick Launch only apps are first
|
||||
return (is_app_quick_launch ? 1 : 0) - (is_new_quick_launch ? 1 : 0);
|
||||
} else if (override_cmp_rv) {
|
||||
// Apps that override storage, record, and install order
|
||||
return (override_cmp_rv);
|
||||
} else if (app_node->storage_order != new_node->storage_order) {
|
||||
// Storage order (smallest first)
|
||||
return prv_comparator_ascending_zero_last(app_node->storage_order, new_node->storage_order);
|
||||
} else if (app_node->record_order != new_node->record_order) {
|
||||
// Record order (smallest first)
|
||||
return prv_comparator_ascending_zero_last(app_node->record_order, new_node->record_order);
|
||||
} else {
|
||||
// AppInstallId (smallest first)
|
||||
return new_node->install_id - app_node->install_id;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_set_storage_order(AppMenuDataSource *source, AppMenuNode *menu_node,
|
||||
AppMenuOrderStorage *storage, bool update_and_take_ownership) {
|
||||
if (!storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < storage->list_length; i++) {
|
||||
const AppInstallId storage_app_id = storage->id_list[i];
|
||||
if (storage_app_id == INSTALL_ID_INVALID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const AppMenuStorageOrder new_storage_order =
|
||||
(AppMenuStorageOrder)i + AppMenuStorageOrderGeneralOrderOffset;
|
||||
|
||||
if (menu_node && (menu_node->install_id == storage_app_id)) {
|
||||
menu_node->storage_order = new_storage_order;
|
||||
if (!update_and_take_ownership) {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (update_and_take_ownership) {
|
||||
AppMenuNode *other_node = prv_find_node_with_install_id(storage_app_id, source);
|
||||
if (other_node) {
|
||||
other_node->storage_order = new_storage_order;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (update_and_take_ownership) {
|
||||
app_free(storage);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_sorted_add(AppMenuDataSource *source, AppMenuNode *menu_node) {
|
||||
// Update the entire list order only if we've just read the order in this context. If we haven't
|
||||
// just read the order, then we're building a list starting from an empty list, so just set the
|
||||
// order for the new node.
|
||||
prv_set_storage_order(source, menu_node, source->order_storage ?: app_order_read_order(),
|
||||
(source->order_storage == NULL));
|
||||
|
||||
// If we're adding the Settings app node to the list and it hasn't received a storage order,
|
||||
// then give it its default order
|
||||
if ((menu_node->install_id == APP_ID_SETTINGS) &&
|
||||
(menu_node->storage_order == AppMenuStorageOrder_NoOrder)) {
|
||||
menu_node->storage_order = AppMenuStorageOrder_SettingsDefaultOrder;
|
||||
}
|
||||
|
||||
source->list = (AppMenuNode *)list_sorted_add(&source->list->node, &menu_node->node,
|
||||
prv_app_node_comparator, true /* ascending */);
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// AppInstallManager Callbacks
|
||||
////////////////////////////////
|
||||
|
||||
typedef struct InstallData {
|
||||
AppInstallId id;
|
||||
AppMenuDataSource *source;
|
||||
InstallEventType event_type;
|
||||
} InstallData;
|
||||
|
||||
void prv_alert_data_source_changed(AppMenuDataSource *data_source) {
|
||||
if (data_source->callbacks.changed) {
|
||||
data_source->callbacks.changed(data_source->callback_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_do_app_added(AppMenuDataSource *source, AppInstallId install_id);
|
||||
static void prv_do_app_removed(AppMenuDataSource *source, AppInstallId install_id);
|
||||
static void prv_do_app_icon_name_updated(AppMenuDataSource *source, AppInstallId install_id);
|
||||
static void prv_do_app_db_cleared(AppMenuDataSource *source);
|
||||
|
||||
void prv_handle_app_event(void *data) {
|
||||
InstallData *install_data = data;
|
||||
AppMenuDataSource *source = install_data->source;
|
||||
AppInstallId install_id = install_data->id;
|
||||
|
||||
switch (install_data->event_type) {
|
||||
case APP_AVAILABLE:
|
||||
prv_do_app_added(source, install_id);
|
||||
break;
|
||||
case APP_REMOVED:
|
||||
prv_do_app_removed(source, install_id);
|
||||
break;
|
||||
case APP_ICON_NAME_UPDATED:
|
||||
prv_do_app_icon_name_updated(source, install_id);
|
||||
break;
|
||||
case APP_DB_CLEARED:
|
||||
prv_do_app_db_cleared(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
kernel_free(install_data);
|
||||
}
|
||||
|
||||
void prv_send_callback_to_app(AppMenuDataSource *data_source, AppInstallId install_id,
|
||||
InstallEventType event_type) {
|
||||
InstallData *install_data = kernel_malloc_check(sizeof(InstallData));
|
||||
*install_data = (InstallData) {
|
||||
.id = install_id,
|
||||
.source = data_source,
|
||||
.event_type = event_type,
|
||||
};
|
||||
process_manager_send_callback_event_to_process(PebbleTask_App,
|
||||
prv_handle_app_event,
|
||||
install_data);
|
||||
}
|
||||
|
||||
//! Must be run from the app task
|
||||
static void prv_do_app_added(AppMenuDataSource *source, AppInstallId install_id) {
|
||||
AppInstallEntry entry;
|
||||
if (!app_install_get_entry_for_install_id(install_id, &entry) ||
|
||||
prv_is_app_filtered_out(&entry, source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_app_with_install_id(&entry, source);
|
||||
prv_alert_data_source_changed(source);
|
||||
}
|
||||
|
||||
//! Called when an application is installed
|
||||
static void prv_app_added_callback(const AppInstallId install_id, void *data) {
|
||||
AppMenuDataSource *data_source = data;
|
||||
prv_send_callback_to_app(data_source, install_id, APP_AVAILABLE);
|
||||
}
|
||||
|
||||
//! Must be run from the app task
|
||||
static void prv_do_app_removed(AppMenuDataSource *source, AppInstallId install_id) {
|
||||
// Don't filter, just always try removing from the list:
|
||||
const bool is_removed = remove_app_with_install_id(install_id, source);
|
||||
if (is_removed) {
|
||||
prv_alert_data_source_changed(source);
|
||||
}
|
||||
}
|
||||
|
||||
//! Called when an application is uninstalled
|
||||
static void prv_app_removed_callback(const AppInstallId install_id, void *data) {
|
||||
AppMenuDataSource *data_source = data;
|
||||
prv_send_callback_to_app(data_source, install_id, APP_REMOVED);
|
||||
}
|
||||
|
||||
//! Must be run from the app task
|
||||
static void prv_do_app_icon_name_updated(AppMenuDataSource *source, AppInstallId install_id) {
|
||||
AppInstallEntry entry;
|
||||
if (!app_install_get_entry_for_install_id(install_id, &entry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppMenuNode *node = prv_find_node_with_install_id(install_id, source);
|
||||
if (prv_is_app_filtered_out(&entry, source)) {
|
||||
if (node == NULL) {
|
||||
// Changed and still excluded:
|
||||
return;
|
||||
}
|
||||
// Changed and is now excluded:
|
||||
prv_unload_node(source, node);
|
||||
} else {
|
||||
// Changed and is now included:
|
||||
if (node == NULL) {
|
||||
add_app_with_install_id(&entry, source);
|
||||
}
|
||||
}
|
||||
prv_alert_data_source_changed(source);
|
||||
}
|
||||
|
||||
static void prv_app_icon_name_updated_callback(const AppInstallId install_id, void *data) {
|
||||
AppMenuDataSource *data_source = data;
|
||||
prv_send_callback_to_app(data_source, install_id, APP_ICON_NAME_UPDATED);
|
||||
}
|
||||
|
||||
//! Must be run from the app task
|
||||
static void prv_do_app_db_cleared(AppMenuDataSource *source) {
|
||||
AppMenuNode *iter = source->list;
|
||||
while (iter) {
|
||||
AppMenuNode *temp = (AppMenuNode *)list_get_next((ListNode *)iter);
|
||||
|
||||
// if the node belonged to the app_db, remove
|
||||
if (app_install_id_from_app_db(iter->install_id)) {
|
||||
prv_unload_node(source, iter);
|
||||
}
|
||||
iter = temp;
|
||||
}
|
||||
|
||||
prv_alert_data_source_changed(source);
|
||||
}
|
||||
|
||||
static void prv_app_db_cleared_callback(const AppInstallId install_id, void *data) {
|
||||
// data is just a pointer to the AppMenuDataSource
|
||||
prv_send_callback_to_app((AppMenuDataSource *)data, INSTALL_ID_INVALID, APP_DB_CLEARED);
|
||||
}
|
||||
|
||||
static bool prv_app_enumerate_callback(AppInstallEntry *entry, void *data) {
|
||||
if (prv_is_app_filtered_out(entry, (AppMenuDataSource *)data)) {
|
||||
return true; // continue
|
||||
}
|
||||
add_app_with_install_id(entry, data);
|
||||
return true; // continue
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// Add / remove helper functions:
|
||||
|
||||
// This function should only be called once per app entry. The icon from the app will either be
|
||||
// loaded and cached or we will load the default system icon that is set by the client.
|
||||
static void prv_load_list_item_icon(AppMenuDataSource *source, AppMenuNode *node) {
|
||||
// Should only call this function if the icon has not been loaded
|
||||
PBL_ASSERTN(node->icon == NULL);
|
||||
|
||||
if (node->icon_resource_id != RESOURCE_ID_INVALID) {
|
||||
// If we have some sort of valid resource_id, try loading it
|
||||
node->icon = gbitmap_create_with_resource_system(node->app_num, node->icon_resource_id);
|
||||
}
|
||||
|
||||
if (!node->icon) {
|
||||
// If we failed to load the app's icon or it didn't have one, use the default. This will either
|
||||
// be NULL or an actual icon...both are fine. And no need to clip the default icon
|
||||
node->icon = source->default_icon;
|
||||
return;
|
||||
}
|
||||
|
||||
// Clip the icon down if needed
|
||||
static const GRect icon_clip = {{0, 0}, {32, 32}};
|
||||
grect_clip(&node->icon->bounds, &icon_clip);
|
||||
}
|
||||
|
||||
static void prv_unload_list_item_icon(const AppMenuDataSource *source, AppMenuNode *node) {
|
||||
// Don't destroy the default icon here, we'll destroy it later.
|
||||
if (node->icon && node->icon != source->default_icon) {
|
||||
gbitmap_destroy(node->icon);
|
||||
node->icon = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_load_list_if_needed(AppMenuDataSource *source) {
|
||||
if (source->is_list_loaded) {
|
||||
return;
|
||||
}
|
||||
source->is_list_loaded = true;
|
||||
|
||||
PBL_ASSERTN(!source->order_storage);
|
||||
source->order_storage = app_order_read_order();
|
||||
|
||||
app_install_enumerate_entries(prv_app_enumerate_callback, source);
|
||||
|
||||
app_free(source->order_storage);
|
||||
source->order_storage = NULL;
|
||||
}
|
||||
|
||||
static void prv_unload_node(const AppMenuDataSource *source, AppMenuNode *node) {
|
||||
prv_unload_list_item_icon(source, node);
|
||||
list_remove((ListNode*)node, (ListNode**)&source->list, NULL);
|
||||
app_free(node->name);
|
||||
app_free(node);
|
||||
}
|
||||
|
||||
static void add_app_with_install_id(const AppInstallEntry *entry, AppMenuDataSource *source) {
|
||||
if (source->is_list_loaded == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppMenuNode *node = app_malloc_check(sizeof(AppMenuNode));
|
||||
*node = (AppMenuNode) {
|
||||
.install_id = entry->install_id,
|
||||
.app_num = app_install_get_app_icon_bank(entry),
|
||||
.icon_resource_id = app_install_entry_get_icon_resource_id(entry),
|
||||
.uuid = entry->uuid,
|
||||
.color = entry->color,
|
||||
.visibility = entry->visibility,
|
||||
.sdk_version = entry->sdk_version,
|
||||
.record_order = entry->record_order,
|
||||
};
|
||||
|
||||
uint8_t len;
|
||||
const char *app_name = app_install_get_custom_app_name(node->install_id);
|
||||
if (!app_name) {
|
||||
app_name = entry->name;
|
||||
}
|
||||
|
||||
len = strlen(app_name) + 1;
|
||||
node->name = app_malloc_check(len);
|
||||
strncpy(node->name, app_name, len);
|
||||
|
||||
prv_sorted_add(source, node);
|
||||
}
|
||||
|
||||
static AppMenuNode * prv_find_node_with_install_id(const AppInstallId install_id,
|
||||
const AppMenuDataSource * const source) {
|
||||
AppMenuNode *node = source->list;
|
||||
while (node != NULL) {
|
||||
if (node->install_id == install_id) {
|
||||
return node;
|
||||
} else {
|
||||
node = (AppMenuNode*)list_get_next((ListNode*)node);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//! @return True if there was app with install_id was found and removed from the list.
|
||||
static bool remove_app_with_install_id(const AppInstallId install_id, AppMenuDataSource *source) {
|
||||
if (source->is_list_loaded == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AppMenuNode *node = prv_find_node_with_install_id(install_id, source);
|
||||
if (node == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prv_unload_node(source, node);
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// public interface
|
||||
|
||||
void app_menu_data_source_init(AppMenuDataSource *source,
|
||||
const AppMenuDataSourceCallbacks *callbacks,
|
||||
void *callback_context) {
|
||||
PBL_ASSERTN(source != NULL);
|
||||
*source = (AppMenuDataSource) {
|
||||
.callback_context = callback_context,
|
||||
};
|
||||
if (callbacks) {
|
||||
source->callbacks = *callbacks;
|
||||
}
|
||||
|
||||
// Register callbacks for app_install_manager updates:
|
||||
static const AppInstallCallback s_app_install_callbacks[NUM_INSTALL_EVENT_TYPES] = {
|
||||
[APP_AVAILABLE] = prv_app_added_callback,
|
||||
[APP_REMOVED] = prv_app_removed_callback,
|
||||
[APP_UPGRADED] = prv_app_removed_callback,
|
||||
[APP_ICON_NAME_UPDATED] = prv_app_icon_name_updated_callback,
|
||||
[APP_DB_CLEARED] = prv_app_db_cleared_callback,
|
||||
};
|
||||
source->app_install_callback_node = (struct AppInstallCallbackNode) {
|
||||
.data = source,
|
||||
.callbacks = s_app_install_callbacks,
|
||||
};
|
||||
app_install_register_callback(&source->app_install_callback_node);
|
||||
}
|
||||
|
||||
void app_menu_data_source_deinit(AppMenuDataSource *source) {
|
||||
app_install_deregister_callback(&source->app_install_callback_node);
|
||||
// Free the AppMenuNodes:
|
||||
AppMenuNode *node = source->list;
|
||||
while (node != NULL) {
|
||||
AppMenuNode * const next = (AppMenuNode*)list_get_next((ListNode*)node);
|
||||
prv_unload_node(source, node);
|
||||
node = next;
|
||||
}
|
||||
|
||||
if (source->default_icon) {
|
||||
gbitmap_destroy(source->default_icon);
|
||||
}
|
||||
|
||||
source->callbacks.changed = NULL;
|
||||
source->is_list_loaded = false;
|
||||
}
|
||||
|
||||
void app_menu_data_source_enable_icons(AppMenuDataSource *source, uint32_t fallback_icon_id) {
|
||||
// should only call this once, and should be passed in a valid resource id.
|
||||
PBL_ASSERTN(source->default_icon == NULL && fallback_icon_id != RESOURCE_ID_INVALID);
|
||||
|
||||
source->show_icons = true;
|
||||
// The return value will be a valid GBitmap* or NULL (because of an OOM that shouldn't ever happen
|
||||
// We will handle both gracefully.
|
||||
source->default_icon = gbitmap_create_with_resource_system(SYSTEM_APP, fallback_icon_id);
|
||||
}
|
||||
|
||||
static uint16_t prv_transform_index(AppMenuDataSource *source, uint16_t index) {
|
||||
if (source->callbacks.transform_index) {
|
||||
return source->callbacks.transform_index(source, index, source->callback_context);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
AppMenuNode* app_menu_data_source_get_node_at_index(AppMenuDataSource *source, uint16_t row_index) {
|
||||
prv_load_list_if_needed(source);
|
||||
return (AppMenuNode*)list_get_at((ListNode*)source->list,
|
||||
prv_transform_index(source, row_index));
|
||||
}
|
||||
|
||||
uint16_t app_menu_data_source_get_count(AppMenuDataSource *source) {
|
||||
prv_load_list_if_needed(source);
|
||||
return list_count((ListNode*)source->list);
|
||||
}
|
||||
|
||||
uint16_t app_menu_data_source_get_index_of_app_with_install_id(AppMenuDataSource *source,
|
||||
AppInstallId install_id) {
|
||||
prv_load_list_if_needed(source);
|
||||
AppMenuNode *node = source->list;
|
||||
uint16_t index = 0;
|
||||
while (node != NULL) {
|
||||
if (node->install_id == install_id) {
|
||||
return prv_transform_index(source, index);
|
||||
}
|
||||
node = (AppMenuNode*)list_get_next((ListNode*)node);
|
||||
++index;
|
||||
}
|
||||
return MENU_INDEX_NOT_FOUND;
|
||||
}
|
||||
|
||||
GBitmap *app_menu_data_source_get_node_icon(AppMenuDataSource *source, AppMenuNode *node) {
|
||||
if (!node->icon && source->show_icons) {
|
||||
// If the icon is currently NULL and we should be showing icons, load the icon
|
||||
prv_load_list_item_icon(source, node);
|
||||
}
|
||||
// Will return the icon if it exists, or NULL if one doesn't.
|
||||
return node->icon;
|
||||
}
|
||||
|
||||
void app_menu_data_source_draw_row(AppMenuDataSource *source, GContext *ctx, Layer *cell_layer,
|
||||
MenuIndex *cell_index) {
|
||||
AppMenuNode *node = app_menu_data_source_get_node_at_index(source, cell_index->row);
|
||||
// Will return an icon or NULL depending on if icons are enabled.
|
||||
GBitmap *bitmap = app_menu_data_source_get_node_icon(source, node);
|
||||
const GCompOp op = (gbitmap_get_format(bitmap) == GBitmapFormat1Bit) ? GCompOpTint : GCompOpSet;
|
||||
graphics_context_set_compositing_mode(ctx, op);
|
||||
menu_cell_basic_draw(ctx, cell_layer, node->name, NULL, bitmap);
|
||||
}
|
127
src/fw/process_management/app_menu_data_source.h
Normal file
127
src/fw/process_management/app_menu_data_source.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
//!@file app_menu_data_source.h
|
||||
//!
|
||||
//! This file provides a utility for populating a MenuLayer with the apps that are currently installed. This should
|
||||
//! only be used by system apps such as the Launcher or the Watchface Selector apps, as it integrates tightly with
|
||||
//! app_install_manager.
|
||||
|
||||
#include "applib/ui/kino/kino_reel.h"
|
||||
#include "applib/ui/menu_layer.h"
|
||||
#include "os/mutex.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/pebble_process_info.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "services/normal/process_management/app_order_storage.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/list.h"
|
||||
|
||||
struct AppMenuDataSource;
|
||||
|
||||
//! This enum provides special cases of app storage order and helps calculate the fixed offset at
|
||||
//! which the general storage order should begin
|
||||
typedef enum AppMenuStorageOrder {
|
||||
AppMenuStorageOrder_NoOrder = 0,
|
||||
AppMenuStorageOrder_SettingsDefaultOrder,
|
||||
|
||||
AppMenuStorageOrderGeneralOrderOffset
|
||||
} AppMenuStorageOrder;
|
||||
|
||||
typedef struct PACKED AppMenuNode {
|
||||
ListNode node;
|
||||
AppInstallId install_id;
|
||||
ResAppNum app_num;
|
||||
uint32_t icon_resource_id;
|
||||
GBitmap *icon;
|
||||
Uuid uuid;
|
||||
GColor color;
|
||||
char *name;
|
||||
ProcessVisibility visibility;
|
||||
Version sdk_version;
|
||||
unsigned int storage_order; //!< See \ref AppMenuStorageOrder for special values of this field
|
||||
unsigned int record_order; //!< 0 means not in the app registry
|
||||
} AppMenuNode;
|
||||
|
||||
// Clang makes the size of enums like AppMenuStorageOrder larger than the minimum size needed to
|
||||
// represent all of the enum's values, so this assert doesn't make since when compiling using Clang
|
||||
#if !__clang__
|
||||
_Static_assert(
|
||||
sizeof(((AppMenuNode *)0)->storage_order) >=
|
||||
sizeof(((AppMenuOrderStorage *)0)->list_length) + sizeof(AppMenuStorageOrder),
|
||||
"The size of AppMenuNode.storage_order must be at least as large as the combined size of "
|
||||
"AppMenuOrderStorage.list_length and the AppMenuStorageOrder enum since the enum provides "
|
||||
"additional values for AppMenuNode.storage_order.");
|
||||
#endif
|
||||
|
||||
typedef bool (*AppMenuFilterCallback)(struct AppMenuDataSource *source, AppInstallEntry *entry);
|
||||
|
||||
typedef void (*AppMenuDataSourceFunc)(void *context);
|
||||
|
||||
typedef uint16_t (*AppMenuDataSourceIndexTransform)(struct AppMenuDataSource *source,
|
||||
uint16_t index, void *context);
|
||||
|
||||
typedef struct AppMenuDataSourceCallbacks {
|
||||
AppMenuFilterCallback filter;
|
||||
AppMenuDataSourceFunc changed;
|
||||
AppMenuDataSourceIndexTransform transform_index;
|
||||
} AppMenuDataSourceCallbacks;
|
||||
|
||||
typedef struct AppMenuDataSource {
|
||||
AppMenuNode *list;
|
||||
AppMenuOrderStorage *order_storage;
|
||||
AppInstallCallbackNode app_install_callback_node;
|
||||
AppMenuDataSourceCallbacks callbacks;
|
||||
void *callback_context;
|
||||
GBitmap *default_icon;
|
||||
bool show_icons;
|
||||
bool is_list_loaded;
|
||||
} AppMenuDataSource;
|
||||
|
||||
//! Initalize the AppMenuDataSource
|
||||
void app_menu_data_source_init(AppMenuDataSource *source,
|
||||
const AppMenuDataSourceCallbacks *handlers,
|
||||
void *callback_context);
|
||||
|
||||
//! Deinitalize the AppMenuDataSource
|
||||
void app_menu_data_source_deinit(AppMenuDataSource *source);
|
||||
|
||||
//! Will load the icons for each `AppMenuNode`. Will automatically be unloaded when
|
||||
//! `app_menu_data_source_deinit` is called.
|
||||
//! @param fallback_icon_id The fallback resource id that should be used if the entry does not have
|
||||
//! an icon.
|
||||
void app_menu_data_source_enable_icons(AppMenuDataSource *source, uint32_t fallback_icon_id);
|
||||
|
||||
//! Returns the AppMenuNode at the given index.
|
||||
AppMenuNode* app_menu_data_source_get_node_at_index(AppMenuDataSource *source, uint16_t row_index);
|
||||
|
||||
//! Returns the AppMenuNode with the given AppInstallId.
|
||||
uint16_t app_menu_data_source_get_index_of_app_with_install_id(AppMenuDataSource *source,
|
||||
AppInstallId install_id);
|
||||
|
||||
//! Returns the count of AppMenuNode's in the source.
|
||||
uint16_t app_menu_data_source_get_count(AppMenuDataSource *source);
|
||||
|
||||
//! Calls `menu_cell_basic_draw` for the AppMenuNode at the given MenuIndex
|
||||
//! @note Will only draw an icon if `show_icons` is set to True.
|
||||
void app_menu_data_source_draw_row(AppMenuDataSource *source, GContext *ctx, Layer *cell_layer,
|
||||
MenuIndex *cell_index);
|
||||
|
||||
//! Returns the allocated `GBitmap *` for the given node. Will return NULL if no icon is loaded.
|
||||
//! @note Will only return an icon if `show_icons` is set to True.
|
||||
GBitmap *app_menu_data_source_get_node_icon(AppMenuDataSource *source, AppMenuNode *node);
|
150
src/fw/process_management/app_run_state.c
Normal file
150
src/fw/process_management/app_run_state.c
Normal file
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* 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 "app_run_state.h"
|
||||
#include "launcher_app_message.h"
|
||||
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/process_manager.h"
|
||||
#include "services/common/system_task.h"
|
||||
#include "system/logging.h"
|
||||
#include "util/attributes.h"
|
||||
|
||||
#define PB_APP_STATE_ENDPOINT_ID 0x34
|
||||
|
||||
typedef struct PACKED {
|
||||
AppState state:8;
|
||||
Uuid uuid;
|
||||
} AppRunState;
|
||||
|
||||
|
||||
static void prv_send_response(void *data) {
|
||||
AppRunState *app_run_state = (AppRunState*)data;
|
||||
|
||||
CommSession *session = comm_session_get_system_session();
|
||||
if (session) {
|
||||
if (comm_session_has_capability(session, CommSessionRunState)) {
|
||||
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
|
||||
bool success = comm_session_send_data(session, PB_APP_STATE_ENDPOINT_ID,
|
||||
(uint8_t*)app_run_state, sizeof(*app_run_state),
|
||||
COMM_SESSION_DEFAULT_TIMEOUT);
|
||||
|
||||
uuid_to_string(&app_run_state->uuid, uuid_buffer);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "AppRunState(0x34) %s sending status: %s - %u",
|
||||
(success ? "success" : "failed"), uuid_buffer, app_run_state->state);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Using deprecated launcher_app_message");
|
||||
const bool is_running = ((app_run_state->state == RUNNING) ? true : false);
|
||||
launcher_app_message_send_app_state_deprecated(&app_run_state->uuid, is_running);
|
||||
}
|
||||
}
|
||||
|
||||
kernel_free(app_run_state);
|
||||
}
|
||||
|
||||
void app_run_state_command(CommSession *session, AppRunStateCommand cmd, const Uuid *uuid) {
|
||||
const AppInstallId install_id = app_install_get_id_for_uuid(uuid);
|
||||
|
||||
// Log most recent communication timestamp:
|
||||
app_install_mark_prioritized(install_id, true /* can_expire */);
|
||||
|
||||
if (install_id == INSTALL_ID_INVALID && cmd != APP_RUN_STATE_STATUS_COMMAND) {
|
||||
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
|
||||
uuid_to_string(uuid, uuid_buffer);
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "No app found with uuid %s", uuid_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case APP_RUN_STATE_RUN_COMMAND:
|
||||
// Launch the application provided it isn't running, otherwise this is a noop
|
||||
app_manager_put_launch_app_event(&(AppLaunchEventConfig) {
|
||||
.id = install_id,
|
||||
.common.reason = APP_LAUNCH_PHONE,
|
||||
});
|
||||
break;
|
||||
case APP_RUN_STATE_STOP_COMMAND:
|
||||
// Stop the application provided it is running, otherwise this is a noop
|
||||
app_install_unmark_prioritized(install_id);
|
||||
if (app_install_is_app_running(install_id)) {
|
||||
process_manager_put_kill_process_event(PebbleTask_App, true);
|
||||
}
|
||||
break;
|
||||
case APP_RUN_STATE_STATUS_COMMAND:
|
||||
// Determine the running application
|
||||
uuid = &app_manager_get_current_app_md()->uuid;
|
||||
if (session != NULL) {
|
||||
// We check the session here as to be backwards compatibile with the 0x31 endpoint and
|
||||
// to avoid repeating code, the endpoint makes use of this function, but since it does
|
||||
// not have an active session (it's session is NULL), it will fall to the else case.
|
||||
AppRunState *app_run_state = kernel_malloc_check(sizeof(AppRunState));
|
||||
app_run_state->state = RUNNING;
|
||||
app_run_state->uuid = *uuid;
|
||||
prv_send_response(app_run_state);
|
||||
} else {
|
||||
launcher_app_message_send_app_state_deprecated(uuid, RUNNING);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Unknown command: %d", cmd);
|
||||
}
|
||||
}
|
||||
|
||||
void app_run_state_protocol_msg_callback(CommSession *session, const uint8_t *data, size_t length) {
|
||||
typedef struct PACKED {
|
||||
uint8_t command;
|
||||
Uuid uuid;
|
||||
} AppStateMessage;
|
||||
|
||||
const AppStateMessage *msg = (const AppStateMessage*)data;
|
||||
|
||||
if (msg->command != APP_RUN_STATE_STATUS_COMMAND) {
|
||||
if (length < sizeof(AppStateMessage)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "length mismatch, expected %"PRIu32" byte(s), got %"PRIu32" bytes",
|
||||
(uint32_t) sizeof(AppStateMessage), (uint32_t) length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
app_run_state_command(session, msg->command, uuid_is_invalid(&msg->uuid) ? NULL : &msg->uuid);
|
||||
}
|
||||
|
||||
void app_run_state_send_update(const Uuid *uuid, AppState app_state) {
|
||||
#ifdef RECOVERY_FW
|
||||
// FIXME: Need to actually factor out this so it's totally removed from PRF, but for now just
|
||||
// pull it all. We can't use this here because we don't initialize app message at all.
|
||||
return;
|
||||
#endif
|
||||
|
||||
// This function deprecates the 0x31 launcher_app_message_send_app_state
|
||||
// providing a different method of interacting with the endpoint. Calls the old
|
||||
// method if the mobile application does not support the new endpoint.
|
||||
|
||||
CommSession *session = comm_session_get_system_session();
|
||||
if (!session) {
|
||||
// If we don't have a comm session open, don't bother sending application messages
|
||||
return;
|
||||
}
|
||||
|
||||
// Offload to KernelBG, because this function is called 2x when switching apps and we want to
|
||||
// be sure not to block KernelMain for 2x 4000ms when the send buffer is full.
|
||||
AppRunState *app_run_state = kernel_malloc(sizeof(AppRunState));
|
||||
app_run_state->uuid = *uuid;
|
||||
app_run_state->state = app_state;
|
||||
system_task_add_callback(prv_send_response, app_run_state);
|
||||
}
|
45
src/fw/process_management/app_run_state.h
Normal file
45
src/fw/process_management/app_run_state.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "services/common/comm_session/session.h"
|
||||
#include "util/uuid.h"
|
||||
|
||||
typedef enum {
|
||||
//! Used as reply from the watch to the phone, to indicate the app is running.
|
||||
//! Or, when pushed from phone to watch, this value will have the effect of launching the app.
|
||||
RUNNING = 0x01,
|
||||
//! Used as reply from the watch to the phone, to indicate the app is not running.
|
||||
//! Or, when pushed from phone to watch, this value will have the effect of killing the app.
|
||||
NOT_RUNNING = 0x02
|
||||
} AppState;
|
||||
|
||||
typedef enum {
|
||||
//! These keys (with accompanying UUID values self FETCH) can be pushed from the phone to
|
||||
//! the watch to launch/kill an app on the watch or query which application is running.
|
||||
//! Backwards compatible for support of deprecated 0x31
|
||||
APP_RUN_STATE_INVALID_COMMAND = 0x00, // Invalid state key, used as a default value
|
||||
APP_RUN_STATE_RUN_COMMAND = 0x01, // Watch -> Phone: App is running, Phone -> Watch: Start app
|
||||
APP_RUN_STATE_STOP_COMMAND = 0x02, // Watch -> Phone: App is stopped, Phone -> Watch: Stop app
|
||||
APP_RUN_STATE_STATUS_COMMAND = 0x03 // Phone -> Watch: Request current app UUID
|
||||
} AppRunStateCommand;
|
||||
|
||||
void app_run_state_send_update(const Uuid *uuid, AppState app_state);
|
||||
|
||||
// Currently only needed for backwards support of the 0x32 endpoint, at which point
|
||||
// it will become static and not exported.
|
||||
void app_run_state_command(CommSession *session, AppRunStateCommand cmd, const Uuid *uuid);
|
28
src/fw/process_management/launch_config.h
Normal file
28
src/fw/process_management/launch_config.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "applib/app_launch_button.h"
|
||||
#include "applib/app_launch_reason.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
|
||||
typedef struct LaunchConfigCommon {
|
||||
AppLaunchReason reason;
|
||||
ButtonId button;
|
||||
const void *args;
|
||||
const CompositorTransition *transition;
|
||||
} LaunchConfigCommon;
|
164
src/fw/process_management/launcher_app_message.c
Normal file
164
src/fw/process_management/launcher_app_message.c
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* 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 "launcher_app_message.h"
|
||||
|
||||
#include "app_run_state.h"
|
||||
#include "applib/app_message/app_message_internal.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include "util/dict.h"
|
||||
|
||||
#define LAUNCHER_MESSAGE_ENDPOINT_ID (0x31)
|
||||
|
||||
typedef enum {
|
||||
//! Used as reply from the watch to the phone, to indicate the app is not running.
|
||||
//! Or, when pushed from phone to watch, this value will have the effect of killing the app.
|
||||
NOT_RUNNING_DEPRECATED = 0x00,
|
||||
//! Used as reply from the watch to the phone, to indicate the app is running.
|
||||
//! Or, when pushed from phone to watch, this value will have the effect of launching the app.
|
||||
RUNNING_DEPRECATED = 0x01,
|
||||
} AppStateDeprecated;
|
||||
|
||||
enum {
|
||||
//! This key/value can be pushed from the phone to the watch to launch
|
||||
//! or kill an app on the watch.
|
||||
RUN_STATE_KEY = 0x01, // TUPLE_UINT8
|
||||
STATE_FETCH_KEY = 0x02, // TUPLE_UINT8
|
||||
};
|
||||
|
||||
static uint8_t s_transaction_id;
|
||||
|
||||
// For unit testing
|
||||
void launcher_app_message_reset(void) {
|
||||
s_transaction_id = 0;
|
||||
}
|
||||
|
||||
void launcher_app_message_send_app_state_deprecated(const Uuid *uuid, bool running) {
|
||||
// Deprecated: 0x31 endpoint, only used by Android versions < 2.2 and iOS
|
||||
AppStateDeprecated app_state = running ? RUNNING_DEPRECATED : NOT_RUNNING_DEPRECATED;
|
||||
|
||||
uint8_t buffer[sizeof(AppMessagePush) + sizeof(Tuple) + sizeof(uint32_t)];
|
||||
AppMessagePush *push_message = (AppMessagePush *)buffer;
|
||||
|
||||
*push_message = (const AppMessagePush) {
|
||||
.header = {
|
||||
.command = CMD_PUSH,
|
||||
.transaction_id = s_transaction_id++,
|
||||
},
|
||||
.uuid = *uuid,
|
||||
};
|
||||
|
||||
uint32_t size = sizeof(Dictionary) + sizeof(Tuple) + sizeof(uint32_t);
|
||||
const Tuplet tuplet = TupletInteger(RUN_STATE_KEY, (uint32_t) app_state);
|
||||
PBL_ASSERTN(DICT_OK == dict_serialize_tuplets_to_buffer(
|
||||
&tuplet, 1, (uint8_t *)&push_message->dictionary, &size));
|
||||
|
||||
comm_session_send_data(comm_session_get_system_session(), LAUNCHER_MESSAGE_ENDPOINT_ID,
|
||||
(const uint8_t *)buffer, sizeof(buffer), COMM_SESSION_DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
static bool prv_has_invalid_length(size_t expected, size_t actual) {
|
||||
if (actual < expected) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Too short");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_receive_push_cmd(CommSession *session,
|
||||
AppMessagePush *push_message, size_t length) {
|
||||
if (prv_has_invalid_length(sizeof(AppMessagePush), length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
|
||||
// Scan the dictionary:
|
||||
const size_t dict_size = length - sizeof(AppMessagePush) + sizeof(Dictionary);
|
||||
DictionaryIterator iter;
|
||||
const Tuple *tuple = dict_read_begin_from_buffer(&iter,
|
||||
(const uint8_t *) &push_message->dictionary,
|
||||
dict_size);
|
||||
while (tuple) {
|
||||
uint8_t cmd = tuple->key;
|
||||
switch (cmd) {
|
||||
case RUN_STATE_KEY:
|
||||
if (tuple->value->uint8 != RUNNING_DEPRECATED) {
|
||||
cmd = APP_RUN_STATE_STOP_COMMAND;
|
||||
} else {
|
||||
cmd = APP_RUN_STATE_RUN_COMMAND;
|
||||
}
|
||||
success = true;
|
||||
break;
|
||||
case STATE_FETCH_KEY:
|
||||
cmd = APP_RUN_STATE_STATUS_COMMAND;
|
||||
success = true;
|
||||
break;
|
||||
default:
|
||||
cmd = APP_RUN_STATE_INVALID_COMMAND;
|
||||
}
|
||||
|
||||
// Call into app_run_state to take the action (to avoid duping code):
|
||||
const Uuid *app_uuid = &push_message->uuid;
|
||||
app_run_state_command(NULL, (AppRunStateCommand)cmd, app_uuid);
|
||||
tuple = dict_read_next(&iter);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static void prv_send_ack_nack_reply(CommSession *session, const uint8_t transaction_id, bool ack) {
|
||||
const AppMessageAck nack_message = {
|
||||
.header = {
|
||||
.command = ack ? CMD_ACK : CMD_NACK,
|
||||
.transaction_id = transaction_id,
|
||||
},
|
||||
};
|
||||
comm_session_send_data(session, LAUNCHER_MESSAGE_ENDPOINT_ID,
|
||||
(const uint8_t *) &nack_message, sizeof(nack_message),
|
||||
COMM_SESSION_DEFAULT_TIMEOUT);
|
||||
}
|
||||
|
||||
void launcher_app_message_protocol_msg_callback_deprecated(CommSession *session,
|
||||
const uint8_t* data, size_t length) {
|
||||
if (prv_has_invalid_length(sizeof(AppMessageHeader), length)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppMessageHeader *message = (AppMessageHeader *) data;
|
||||
bool ack = false;
|
||||
switch (message->command) {
|
||||
case CMD_PUSH: {
|
||||
// Incoming message:
|
||||
ack = prv_receive_push_cmd(session, (AppMessagePush *) message, length);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_ACK:
|
||||
case CMD_NACK:
|
||||
// Ignore ACK / NACKs
|
||||
return;
|
||||
|
||||
default:
|
||||
// Ignore everything else
|
||||
break;
|
||||
}
|
||||
|
||||
prv_send_ack_nack_reply(session, message->transaction_id, ack);
|
||||
}
|
26
src/fw/process_management/launcher_app_message.h
Normal file
26
src/fw/process_management/launcher_app_message.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/uuid.h"
|
||||
|
||||
//! Launcher App Message is deprecated and on Android >= 2.3 and other devices that pass the
|
||||
//! support flags for the AppRunState endpoint will use that endpoint (0x34) instead. That
|
||||
//! endpoint should be used for sending messages on start/stop status of applications and
|
||||
//! sending/recieving application states. The LauncherAppMessage endpoint is kept for
|
||||
//! backwards compability with older mobile applications.
|
||||
void launcher_app_message_send_app_state_deprecated(const Uuid *uuid, bool running);
|
25
src/fw/process_management/pebble_process_info.c
Normal file
25
src/fw/process_management/pebble_process_info.c
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 "pebble_process_info.h"
|
||||
|
||||
int version_compare(Version a, Version b) {
|
||||
if (a.major != b.major) {
|
||||
return a.major - b.major;
|
||||
}
|
||||
return a.minor - b.minor;
|
||||
}
|
||||
|
274
src/fw/process_management/pebble_process_info.h
Normal file
274
src/fw/process_management/pebble_process_info.h
Normal file
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup App
|
||||
//! @{
|
||||
|
||||
//! Application metadata flags.
|
||||
//! Flags can be combined using the `|` operator.
|
||||
//! @see PBL_PROCESS_INFO
|
||||
typedef enum {
|
||||
//! Use to indicate the process is a "standard" app.
|
||||
//! The system will show the app in the main menu.
|
||||
PROCESS_INFO_STANDARD_APP = 0,
|
||||
//! Use to indicate the process is a watchface.
|
||||
//! The system will show the process in the watchfaces menu.
|
||||
PROCESS_INFO_WATCH_FACE = 1 << 0,
|
||||
//! Use to hide the process.
|
||||
PROCESS_INFO_VISIBILITY_HIDDEN = 1 << 1,
|
||||
//! Use to hide the process, unless there is ongoing communication with
|
||||
//! the companion smartphone application.
|
||||
PROCESS_INFO_VISIBILITY_SHOWN_ON_COMMUNICATION = 1 << 2,
|
||||
//! Use to indicate the process allows Javascript API access
|
||||
PROCESS_INFO_ALLOW_JS = 1 << 3,
|
||||
//! Use to indicate the process should have a worker.bin installed as well.
|
||||
PROCESS_INFO_HAS_WORKER = 1 << 4,
|
||||
//! True, if process uses RockyJS APIs
|
||||
PROCESS_INFO_ROCKY_APP = 1 << 5,
|
||||
|
||||
//! Bitmask, to store compile time platform
|
||||
PROCESS_INFO_PLATFORM_MASK = 0xf << 6,
|
||||
//! SDK older than 4.2 doesn't store any value
|
||||
PROCESS_INFO_PLATFORM_UNKNOWN = 0x0 << 6,
|
||||
PROCESS_INFO_PLATFORM_APLITE = 0x1 << 6,
|
||||
//! Values that are actually added by SDK 4.2+
|
||||
PROCESS_INFO_PLATFORM_BASALT = 0x2 << 6,
|
||||
PROCESS_INFO_PLATFORM_CHALK = 0x3 << 6,
|
||||
PROCESS_INFO_PLATFORM_DIORITE = 0x4 << 6,
|
||||
PROCESS_INFO_PLATFORM_EMERY = 0x5 << 6,
|
||||
} PebbleProcessInfoFlags;
|
||||
|
||||
//! @} // group App
|
||||
//! @} // group Foundation
|
||||
|
||||
// struct PebbleProcessInfo change log
|
||||
// ================================
|
||||
// struct_version (little endian):
|
||||
// 0x0800 -- sdk_version and process_version uint16_t fields added (Grand Slam / 1.7)
|
||||
// .major:0x08 .minor:0x01 -- all version fields split up into minor/major; uuid field appended (Junior Whopper / 2.0?)
|
||||
// .major:0x08 .minor:0x02 -- 2.0, added resource crc and resource timestamp
|
||||
// .major:0x09 .minor:0x00 -- 2.0, no more reloc_list_start
|
||||
// .major:0x10 .minor:0x00 -- 2.0, added virtual_size
|
||||
#define PROCESS_INFO_CURRENT_STRUCT_VERSION_MAJOR 0x10
|
||||
#define PROCESS_INFO_CURRENT_STRUCT_VERSION_MINOR 0x0
|
||||
|
||||
// process info version for last know 1.x
|
||||
// let this be a warning to engineers everywhere
|
||||
// who want to design a system with fancy versioning and
|
||||
// support
|
||||
#define PROCESS_INFO_LEGACY_STRUCT_VERSION_MAJOR 0x08
|
||||
|
||||
// SDK change log
|
||||
// ================================
|
||||
// sdk.major:0x4 .minor:0x0 -- Bump the SDK version to make 1.x and 2.x apps distinguishable
|
||||
// sdk.major:0x5 .minor:0x0 -- Bump the SDK version for breaking AppMessage changes b/t 2.x alpha and beta releases
|
||||
// sdk.major:0x5 .minor:0x1 -- Added additional API functions (MenuLayer callbacks)
|
||||
// sdk.major:0x5 .minor:0x2 -- Changed app heap double free behaviour.
|
||||
// sdk.major:0x5 .minor:0x3 -- Added number_window_get_window (API v2.0 revision 12)
|
||||
// sdk.major:0x5 .minor:0x4 -- Added gbitmap_create_blank (API v2.0 revision 13) and click_recognizer_is_repeating (rev 14)
|
||||
// sdk.major:0x5 .minor:0x5 -- Added accel_raw_data_service_subscribe and related types (rev 15)
|
||||
// sdk.major:0x5 .minor:0x6 -- Added background worker APIs (rev 16)
|
||||
// sdk.major:0x5 .minor:0x7 -- Added heap_bytes_free / heap_bytes_used (rev 17)
|
||||
// sdk.major:0x5 .minor:0x8 -- Added compass APIs (rev 18)
|
||||
// sdk.major:0x5 .minor:0x9 -- Added Uuid utility APIs (rev 19)
|
||||
// sdk.major:0x5 .minor:0xa -- Fixed gpath behaviour, added gpath_draw_filled_legacy. (rev 20)
|
||||
// sdk.major:0x5 .minor:0xb -- Added custom animation curves (rev 21)
|
||||
// sdk.major:0x5 .minor:0xc -- Added API for model, color and firmware version of watch (rev 22)
|
||||
// sdk.major:0x5 .minor:0xd -- Added direct access to frame buffer (rev 23)
|
||||
// sdk.major:0x5 .minor:0xe -- Added wakeup API, app_launch_reason (rev 24)
|
||||
// sdk.major:0x5 .minor:0xf -- Added clock_is_timezone_set in preparation for timezone support (rev 25)
|
||||
// sdk.major:0x5 .minor:0x10 -- Added the first i18n API: get_locale (rev 26)
|
||||
// sdk.major:0x5 .minor:0x11 -- Added second i18n API: setlocale (rev 27)
|
||||
// sdk.major:0x5 .minor:0x13 -- Export mktime (rev 29)
|
||||
// sdk.major:0x5 .minor:0x14 -- Rev 30 was a move of several color APIs as to not conflict with release 2.9 sdk. Several of these functions were removed after rev 55 for 3.0-beta10
|
||||
// sdk.major:0x5 .minor:0x15 -- Added timezone APIs (rev 31)
|
||||
// sdk.major:0x5 .minor:0x16 -- Added 3.0 animation API (rev 32)
|
||||
// sdk.major:0x5 .minor:0x17 -- Export new gbitmap accessors (rev 33)
|
||||
// sdk.major:0x5 .minor:0x18 -- Export gbitmap_sequence API (rev 34)
|
||||
// sdk.major:0x5 .minor:0x19 -- Export gbitmap_create_from_png_data API (rev 35)
|
||||
// sdk.major:0x5 .minor:0x20 -- Export new complex animations API (rev 36)
|
||||
// sdk.major:0x5 .minor:0x21 -- Export missing accessors animations APIs (rev 37)
|
||||
// sdk.major:0x5 .minor:0x22 -- Export launch_get_args() API (rev 38)
|
||||
// sdk.major:0x5 .minor:0x23 -- Export 3.0 menu_layer_create() and menu_layer_shadow_enable() (rev 39)
|
||||
// sdk.major:0x5 .minor:0x24 -- Added additional calls to gbitmap_sequence (rev 40)
|
||||
// sdk.major:0x5 .minor:0x25 -- Export antialiased flag and stroke width in GContext (rev 41)
|
||||
// sdk.major:0x5 .minor:0x26 -- Added 3.0 ActionBar (rev 42)
|
||||
// sdk.major:0x5 .minor:0x27 -- Added gbitmap_sequence_update_bitmap_by_elapsed (rev 43)
|
||||
// sdk.major:0x5 .minor:0x28 -- Export menu_layer_cell_is_highlighted() and gbitmap_create_palettized_from_1bit() (rev 44)
|
||||
// sdk.major:0x5 .minor:0x29 -- Export graphics_draw_rotated_bitmap() API (rev 45)
|
||||
// sdk.major:0x5 .minor:0x2a -- Added action_bar_layer_set_icon_press_animation (rev 46)
|
||||
// sdk.major:0x5 .minor:0x2b -- Created TextLayerLegacy2 (rev 47)
|
||||
// sdk.major:0x5 .minor:0x2c -- Added GDrawCommand (rev 48)
|
||||
// sdk.major:0x5 .minor:0x2d -- Added property_animation_update_uint32 (rev 49)
|
||||
// sdk.major:0x5 .minor:0x2e -- Added gpath_draw_outline_open (rev 50)
|
||||
// sdk.major:0x5 .minor:0x2f -- Fixed legacy time() support (rev 51)
|
||||
// sdk.major:0x5 .minor:0x30 -- Deprecate inverter layer (rev 52)
|
||||
// sdk.major:0x5 .minor:0x31 -- Added default menu layer colors (rev 53)
|
||||
// sdk.major:0x5 .minor:0x32 -- Added menu_layer_set_callbacks (rev 54)
|
||||
// sdk.major:0x5 .minor:0x33 -- API cleanup, loop_count --> play_count (rev 55)
|
||||
// sdk.major:0x5 .minor:0x34 -- Added menu_layer_pad_bottom_enable (rev 56)
|
||||
// sdk.major:0x5 .minor:0x35 -- Export initial version of StatusBarLayer (rev 57)
|
||||
// sdk.major:0x5 .minor:0x36 -- Export difftime (rev 58)
|
||||
// sdk.major:0x5 .minor:0x37 -- Fixed legacy time_ms() support (rev 59)
|
||||
// sdk.major:0x5 .minor:0x38 -- Export gcolor_legible_over (rev 60)
|
||||
// sdk.major:0x5 .minor:0x39 -- Export property_animation_update_gcolor8 (rev 61)
|
||||
// sdk.major:0x5 .minor:0x3a -- Export app_focus_service_subscribe_handlers (rev 62)
|
||||
// sdk.major:0x5 .minor:0x3b -- Export ActionMenu APIs (rev 63)
|
||||
// sdk.major:0x5 .minor:0x3c -- Export Smartstrap APIs (rev 64)
|
||||
// sdk.major:0x5 .minor:0x3d -- Added support for timeline resources in PBWs (rev 65)
|
||||
// sdk.major:0x5 .minor:0x3e -- Update Connection Service APIs (rev 66)
|
||||
// sdk.major:0x5 .minor:0x3f -- Enabled support for 8K App Messages (rev 67)
|
||||
// sdk.major:0x5 .minor:0x40 -- Allow disabling of error dialogs for the voice API (rev 68)
|
||||
// sdk.major:0x5 .minor:0x41 -- DEG_TO_TRIGANGLE (rev 69)
|
||||
// sdk.major:0x5 .minor:0x42 -- Export gbitmap_get_data_row_info (rev 70)
|
||||
// sdk.major:0x5 .minor:0x43 -- Export ContentIndicator (rev 71)
|
||||
// sdk.major:0x5 .minor:0x45 -- Export grect_inset (rev 73)
|
||||
// sdk.major:0x5 .minor:0x46 -- Export fill_radial, draw_arc, etc. (rev 74)
|
||||
// sdk.major:0x5 .minor:0x47 -- Text flow and scroll layer pagination (rev 75)
|
||||
// sdk.major:0x5 .minor:0x48 -- Export menu_layer_is_index_selected and round menu layer cell heights (rev 76)
|
||||
// sdk.major:0x5 .minor:0x49 -- Added font v3 (no API changes) (rev 76)
|
||||
// sdk.major:0x5 .minor:0x4a -- HealthAPI (rev 77)
|
||||
// sdk.major:0x5 .minor:0x4b -- More time utility functions (rev 78)
|
||||
// sdk.major:0x5 .minor:0x4c -- More Health service API calls (rev 79)
|
||||
// sdk.major:0x5 .minor:0x4d -- Add health_service_get_measurement_system_for_display() (rev 80)
|
||||
// sdk.major:0x5 .minor:0x4e -- Export gdraw_command_frame_get_command_list() (rev 81)
|
||||
// sdk.major:0x5 .minor:0x4f -- Add new version of gcolor_equal() and deprecate old version (rev 82)
|
||||
// sdk.major:0x5 .minor:0x50 -- 4.0: Add health service expansion, UnobstructedArea, AppGlance, AppExitReason, result duration (rev 83)
|
||||
// sdk.major:0x5 .minor:0x51 -- Add memory_cache_flush() (rev 84)
|
||||
// sdk.major:0x5 .minor:0x52 -- rocky_event_loop_with_resource (rev. 85)
|
||||
// sdk.major:0x5 .minor:0x53 -- Add health_service heart rate sampling period support (rev 86)
|
||||
// sdk.major:0x5 .minor:0x54 -- Add PlatformType enum and defines (rev 87)
|
||||
// sdk.major:0x5 .minor:0x55 -- Preferred Content Size (rev 88)
|
||||
// sdk.major:0x5 .minor:0x56 -- Add PlatformType enum and defines (rev 89)
|
||||
|
||||
#define PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR 0x5
|
||||
#define PROCESS_INFO_CURRENT_SDK_VERSION_MINOR 0x56
|
||||
|
||||
// The first SDK to ship with 2.x APIs
|
||||
#define PROCESS_INFO_FIRST_2X_SDK_VERSION_MAJOR 0x4
|
||||
#define PROCESS_INFO_FIRST_2X_SDK_VERSION_MINOR 0x0
|
||||
|
||||
// The first SDK to ship with 3.x APIs
|
||||
#define PROCESS_INFO_FIRST_3X_SDK_VERSION_MAJOR 0x5
|
||||
#define PROCESS_INFO_FIRST_3X_SDK_VERSION_MINOR 0x16
|
||||
|
||||
// The first SDK to ship with 4.x APIs
|
||||
#define PROCESS_INFO_FIRST_4X_SDK_VERSION_MAJOR 0x5
|
||||
#define PROCESS_INFO_FIRST_4X_SDK_VERSION_MINOR 0x50
|
||||
|
||||
// The first SDK that has a stable Rocky.js contract + stores platforms in binary
|
||||
#define PROCESS_INFO_FIRST_4_2_X_SDK_VERSION_MAJOR 0x5
|
||||
#define PROCESS_INFO_FIRST_4_2_X_SDK_VERSION_MINOR 0x54
|
||||
|
||||
#define PROCESS_NAME_BYTES 32
|
||||
#define COMPANY_NAME_BYTES 32
|
||||
|
||||
//! @internal
|
||||
//! Version data structure with minor & major versions: When making non-backwards-compatible changes,
|
||||
//! the major version should get bumped. When making a change (e.g. to the PebbleProcessInfo struct) that is backwards
|
||||
//! compatible (e.g. adding a field at the end), you should only bump the minor version.
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
uint8_t major; //!< "compatibility" version number
|
||||
uint8_t minor;
|
||||
} Version;
|
||||
|
||||
//! @return 0 if a and b are equal, >0 if a > b, <0 if b > a
|
||||
int version_compare(Version a, Version b);
|
||||
|
||||
//! @internal
|
||||
// WARNING: changes in this struct must be reflected in:
|
||||
// - tintin/waftools/inject_metadata.py
|
||||
// - iOS/PebblePrivateKit/PebblePrivateKit/PBBundle.m
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
char header[8]; //!< Sentinal value, should always be 'PBLAPP'
|
||||
Version struct_version; //!< version of this structure's format
|
||||
Version sdk_version; //!< version of the SDK used to build this process
|
||||
Version process_version; //!< version of the process
|
||||
uint16_t load_size; //!< size of the binary in flash, including this metadata but not the reloc table
|
||||
uint32_t offset; //!< The entry point of this executable
|
||||
uint32_t crc; //!< CRC of the data only, ie, not including this struct or the reloc table at the end
|
||||
char name[PROCESS_NAME_BYTES]; //!< Name to display on the menu
|
||||
char company[COMPANY_NAME_BYTES]; //!< Name of the maker of this process
|
||||
uint32_t icon_resource_id; //!< Resource ID within this bank to use as a 32x32 icon
|
||||
uint32_t sym_table_addr; //!< The system will poke the sdk's symbol table address into this field on load
|
||||
uint32_t flags; //!< Bitwise OR of PebbleProcessInfoFlags
|
||||
uint32_t num_reloc_entries; //!< The number of entries in the address relocation list
|
||||
struct __attribute__((__packed__)) {
|
||||
uint8_t byte0;
|
||||
uint8_t byte1;
|
||||
uint8_t byte2;
|
||||
uint8_t byte3;
|
||||
uint8_t byte4;
|
||||
uint8_t byte5;
|
||||
uint8_t byte6;
|
||||
uint8_t byte7;
|
||||
uint8_t byte8;
|
||||
uint8_t byte9;
|
||||
uint8_t byte10;
|
||||
uint8_t byte11;
|
||||
uint8_t byte12;
|
||||
uint8_t byte13;
|
||||
uint8_t byte14;
|
||||
uint8_t byte15;
|
||||
} uuid; //!< The process's UUID
|
||||
uint32_t resource_crc; //!< CRC of the resource data only
|
||||
uint32_t resource_timestamp; //!< timestamp of the resource data
|
||||
uint16_t virtual_size; //!< The total amount of memory used by the process (.text + .data + .bss)
|
||||
} PebbleProcessInfo;
|
||||
|
||||
//! @internal
|
||||
typedef struct __attribute__((__packed__)) {
|
||||
char header[8]; //!< Sentinal value, should always be 'PBLAPP'
|
||||
Version struct_version; //!< version of this structure's format
|
||||
Version sdk_version; //!< version of the SDK used to build this process
|
||||
Version process_version; //!< version of the process
|
||||
uint16_t load_size; //!< size of the binary in flash, including this metadata but not the reloc table
|
||||
uint32_t offset; //!< The entry point of this executable
|
||||
uint32_t crc; //!< CRC of the data only, ie, not including this struct or the reloc table at the end
|
||||
char name[PROCESS_NAME_BYTES]; //!< Name to display on the menu
|
||||
char company[COMPANY_NAME_BYTES]; //!< Name of the maker of this process
|
||||
uint32_t icon_resource_id; //!< Resource ID within this process's bank to use as a 32x32 icon
|
||||
uint32_t sym_table_addr; //!< The system will poke the sdk's symbol table address into this field on load
|
||||
uint32_t flags; //!< Bitwise OR of PebbleProcessInfoFlags
|
||||
uint32_t reloc_list_start; //!< The offset of the address relocation list
|
||||
uint32_t num_reloc_entries; //!< The number of entries in the address relocation list
|
||||
struct __attribute__((__packed__)) {
|
||||
uint8_t byte0;
|
||||
uint8_t byte1;
|
||||
uint8_t byte2;
|
||||
uint8_t byte3;
|
||||
uint8_t byte4;
|
||||
uint8_t byte5;
|
||||
uint8_t byte6;
|
||||
uint8_t byte7;
|
||||
uint8_t byte8;
|
||||
uint8_t byte9;
|
||||
uint8_t byte10;
|
||||
uint8_t byte11;
|
||||
uint8_t byte12;
|
||||
uint8_t byte13;
|
||||
uint8_t byte14;
|
||||
uint8_t byte15;
|
||||
} uuid; //!< The process's UUID
|
||||
} LegacyPebbleAppInfo;
|
||||
|
||||
|
293
src/fw/process_management/pebble_process_md.c
Normal file
293
src/fw/process_management/pebble_process_md.c
Normal file
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* 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 "pebble_process_md.h"
|
||||
|
||||
#include "process_loader.h"
|
||||
#include "board/display.h"
|
||||
#include <string.h>
|
||||
|
||||
//////////////////////
|
||||
// Md Field Accessors
|
||||
//////////////////////
|
||||
|
||||
const char* process_metadata_get_name(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->name;
|
||||
} else if (md->process_storage == ProcessStorageResource) {
|
||||
return ((const PebbleProcessMdResource*) md)->name;
|
||||
}
|
||||
return ((const PebbleProcessMdSystem*) md)->name;
|
||||
}
|
||||
|
||||
uint32_t process_metadata_get_size_bytes(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->size_bytes;
|
||||
} else if (md->process_storage == ProcessStorageResource) {
|
||||
return ((const PebbleProcessMdResource*) md)->size_bytes;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Version process_metadata_get_process_version(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->process_version;
|
||||
}
|
||||
return (Version) { 0, 0 };
|
||||
}
|
||||
|
||||
Version process_metadata_get_sdk_version(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->sdk_version;
|
||||
}
|
||||
return (Version) { PROCESS_INFO_CURRENT_SDK_VERSION_MAJOR, PROCESS_INFO_CURRENT_SDK_VERSION_MINOR };
|
||||
}
|
||||
|
||||
ProcessAppRunLevel process_metadata_get_run_level(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ProcessAppRunLevelNormal;
|
||||
} else if (md->process_storage == ProcessStorageResource) {
|
||||
return ProcessAppRunLevelNormal;
|
||||
}
|
||||
return ((const PebbleProcessMdSystem*) md)->run_level;
|
||||
}
|
||||
|
||||
int process_metadata_get_code_bank_num(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->code_bank_num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_metadata_get_res_bank_num(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->res_bank_num;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ResourceVersion process_metadata_get_res_version(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->res_version;
|
||||
}
|
||||
return (ResourceVersion) { 0, 0 } ;
|
||||
}
|
||||
|
||||
const uint8_t *process_metadata_get_build_id(const PebbleProcessMd *md) {
|
||||
if (md->process_storage == ProcessStorageFlash) {
|
||||
return ((const PebbleProcessMdFlash*) md)->build_id;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// Md Builders
|
||||
//////////////////////
|
||||
|
||||
static void prv_init_from_info_common(PebbleProcessMd *common, const PebbleProcessInfo *info,
|
||||
PebbleTask task, ProcessStorage process_storage) {
|
||||
|
||||
memcpy(&common->uuid, &info->uuid, sizeof(Uuid));
|
||||
common->process_storage = process_storage;
|
||||
|
||||
// Flags
|
||||
common->process_type = process_metadata_flags_process_type(info->flags, task);
|
||||
common->visibility = process_metadata_flags_visibility(info->flags);
|
||||
common->allow_js = process_metadata_flags_allow_js(info->flags);
|
||||
common->has_worker = process_metadata_flags_has_worker(info->flags);
|
||||
common->is_rocky_app = process_metadata_flags_rocky_app(info->flags);
|
||||
common->stored_sdk_platform = process_metadata_flags_stored_sdk_platform(info->flags);
|
||||
common->is_unprivileged = true;
|
||||
// We don't know the load address of the process until the process is
|
||||
// actually loaded, so we can't convert the entry point's offset into
|
||||
// an address until it's actually been loaded into that address.
|
||||
// Just shove the unmodified offset into the struct and let the
|
||||
// process loader convert it into an absolute address.
|
||||
// TODO: refactor the PebbleProcessMd stuff so that metadata that
|
||||
// isn't needed before load or can't be fully resolved until then
|
||||
// is left out of PebbleProcessMdCommon.
|
||||
common->main_func = (void *)(uintptr_t)info->offset;
|
||||
}
|
||||
|
||||
|
||||
void process_metadata_init_with_flash_header(PebbleProcessMdFlash *md,
|
||||
const PebbleProcessInfo *info, int code_bank_num, PebbleTask task, uint8_t *build_id_buffer) {
|
||||
|
||||
*md = (PebbleProcessMdFlash){};
|
||||
prv_init_from_info_common(&md->common, info, task, ProcessStorageFlash);
|
||||
|
||||
// Flash app specific fields
|
||||
strncpy(md->name, info->name, sizeof(md->name));
|
||||
md->name[sizeof(md->name) - 1] = 0;
|
||||
|
||||
md->size_bytes = info->virtual_size;
|
||||
|
||||
md->process_version = info->process_version;
|
||||
md->sdk_version = info->sdk_version;
|
||||
|
||||
md->code_bank_num = code_bank_num;
|
||||
md->res_bank_num = code_bank_num;
|
||||
|
||||
md->res_version = (ResourceVersion) {
|
||||
.crc = info->resource_crc,
|
||||
.timestamp = info->resource_timestamp
|
||||
};
|
||||
|
||||
if (build_id_buffer) {
|
||||
memcpy(md->build_id, build_id_buffer, BUILD_ID_EXPECTED_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
void process_metadata_init_with_resource_header(PebbleProcessMdResource *md,
|
||||
const PebbleProcessInfo *info, int bin_resource_id, PebbleTask task) {
|
||||
|
||||
*md = (PebbleProcessMdResource){};
|
||||
prv_init_from_info_common(&md->common, info, task, ProcessStorageResource);
|
||||
|
||||
// Resource app specific fields
|
||||
strncpy(md->name, info->name, sizeof(md->name));
|
||||
md->name[sizeof(md->name) - 1] = 0;
|
||||
|
||||
md->size_bytes = info->virtual_size;
|
||||
md->bin_resource_id = bin_resource_id;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// PebbleProcessInfoFlags Helpers
|
||||
//////////////////////////////////
|
||||
|
||||
ProcessVisibility process_metadata_flags_visibility(PebbleProcessInfoFlags flags) {
|
||||
if (flags & PROCESS_INFO_VISIBILITY_HIDDEN) {
|
||||
return ProcessVisibilityHidden;
|
||||
} else if (flags & PROCESS_INFO_VISIBILITY_SHOWN_ON_COMMUNICATION) {
|
||||
return ProcessVisibilityShownOnCommunication;
|
||||
} else {
|
||||
return ProcessVisibilityShown;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessType process_metadata_flags_process_type(PebbleProcessInfoFlags flags, PebbleTask task) {
|
||||
// Flags
|
||||
if (flags & PROCESS_INFO_WATCH_FACE) {
|
||||
return ProcessTypeWatchface;
|
||||
} else if (task == PebbleTask_Worker) {
|
||||
// BG_TODO: Set a bit in the PebbleProcessInfo to indicate it's a worker instead of having to
|
||||
// pass in the is_worker argument. Need to update process_metadata_get_flags_bitfield() to
|
||||
// match as well.
|
||||
return ProcessTypeWorker;
|
||||
} else {
|
||||
return ProcessTypeApp;
|
||||
}
|
||||
}
|
||||
|
||||
bool process_metadata_flags_allow_js(PebbleProcessInfoFlags flags) {
|
||||
return ((flags & PROCESS_INFO_ALLOW_JS) != 0);
|
||||
}
|
||||
|
||||
bool process_metadata_flags_has_worker(PebbleProcessInfoFlags flags) {
|
||||
return ((flags & PROCESS_INFO_HAS_WORKER) != 0);
|
||||
}
|
||||
|
||||
bool process_metadata_flags_rocky_app(PebbleProcessInfoFlags flags) {
|
||||
return ((flags & PROCESS_INFO_ROCKY_APP) != 0);
|
||||
}
|
||||
|
||||
uint16_t process_metadata_flags_stored_sdk_platform(PebbleProcessInfoFlags flags) {
|
||||
return (flags & PROCESS_INFO_PLATFORM_MASK);
|
||||
}
|
||||
|
||||
static const Version first_3x_version = {
|
||||
PROCESS_INFO_FIRST_3X_SDK_VERSION_MAJOR,
|
||||
PROCESS_INFO_FIRST_3X_SDK_VERSION_MINOR,
|
||||
};
|
||||
static const Version first_4x_version = {
|
||||
PROCESS_INFO_FIRST_4X_SDK_VERSION_MAJOR,
|
||||
PROCESS_INFO_FIRST_4X_SDK_VERSION_MINOR,
|
||||
};
|
||||
static const Version first_4_2_version = {
|
||||
PROCESS_INFO_FIRST_4_2_X_SDK_VERSION_MAJOR,
|
||||
PROCESS_INFO_FIRST_4_2_X_SDK_VERSION_MINOR,
|
||||
};
|
||||
|
||||
|
||||
PlatformType process_metadata_get_app_sdk_platform(const PebbleProcessMd *md) {
|
||||
if (!md->is_unprivileged) {
|
||||
return PBL_PLATFORM_TYPE_CURRENT;
|
||||
}
|
||||
|
||||
const Version app_sdk_version = process_metadata_get_sdk_version(md);
|
||||
|
||||
// 2.0 <= SDK < 3.0
|
||||
if (version_compare(app_sdk_version, first_3x_version) < 0) {
|
||||
// 2.x SDKs didn't support anything but Aplite
|
||||
return PlatformTypeAplite;
|
||||
}
|
||||
// 3.0 <= SDK < 4.0
|
||||
if (version_compare(app_sdk_version, first_4x_version) < 0) {
|
||||
return PBL_PLATFORM_SWITCH(PBL_PLATFORM_TYPE_CURRENT,
|
||||
/* aplite */ PlatformTypeAplite, // unreachable, since we don't build for Tintin anymore
|
||||
/* basalt */ PlatformTypeBasalt,
|
||||
/* chalk */ PlatformTypeChalk,
|
||||
/* diorite */ PlatformTypeAplite, // there's was no Diorite SDK prior to 4.0
|
||||
/* emery */ PlatformTypeBasalt);
|
||||
}
|
||||
// 4.0 <= SDK < 4.2
|
||||
if (version_compare(app_sdk_version, first_4_2_version) < 0) {
|
||||
return PBL_PLATFORM_SWITCH(PBL_PLATFORM_TYPE_CURRENT,
|
||||
/* aplite */ PlatformTypeAplite, // unreachable, since we don't build for Tintin anymore
|
||||
/* basalt */ PlatformTypeBasalt,
|
||||
/* chalk */ PlatformTypeChalk,
|
||||
/* diorite */ PlatformTypeDiorite, // there's was no Aplite SDK after 4.0
|
||||
/* emery */ PlatformTypeBasalt);
|
||||
}
|
||||
|
||||
// 4.2 <= SDK --> the flags should be filled correctly.
|
||||
const uint32_t stored_value = md->stored_sdk_platform;
|
||||
switch (stored_value) {
|
||||
case PROCESS_INFO_PLATFORM_APLITE:
|
||||
return PlatformTypeAplite;
|
||||
case PROCESS_INFO_PLATFORM_BASALT:
|
||||
return PlatformTypeBasalt;
|
||||
case PROCESS_INFO_PLATFORM_CHALK:
|
||||
return PlatformTypeChalk;
|
||||
case PROCESS_INFO_PLATFORM_DIORITE:
|
||||
return PlatformTypeDiorite;
|
||||
case PROCESS_INFO_PLATFORM_EMERY:
|
||||
return PlatformTypeEmery;
|
||||
default: {
|
||||
// If we encounter an unknown platform, we assume that it's meant for the current platform
|
||||
// (as it's most-likely a system-app). This is not a security risk as developers could always
|
||||
// patch the binaries as they wish anyway
|
||||
return PBL_PLATFORM_TYPE_CURRENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessAppSDKType process_metadata_get_app_sdk_type(const PebbleProcessMd *md) {
|
||||
if (!md->is_unprivileged) {
|
||||
return ProcessAppSDKType_System;
|
||||
} else {
|
||||
const Version app_sdk_version = process_metadata_get_sdk_version(md);
|
||||
|
||||
if (version_compare(app_sdk_version, first_3x_version) < 0) {
|
||||
return ProcessAppSDKType_Legacy2x;
|
||||
} else if (version_compare(app_sdk_version, first_4x_version) < 0) {
|
||||
return ProcessAppSDKType_Legacy3x;
|
||||
} else {
|
||||
return ProcessAppSDKType_4x;
|
||||
}
|
||||
}
|
||||
}
|
193
src/fw/process_management/pebble_process_md.h
Normal file
193
src/fw/process_management/pebble_process_md.h
Normal file
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "applib/platform.h"
|
||||
#include "kernel/pebble_tasks.h"
|
||||
#include "resource/resource.h"
|
||||
#include "util/build_id.h"
|
||||
|
||||
#include "pebble_process_info.h"
|
||||
|
||||
#include "util/uuid.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef void (*PebbleMain)(void);
|
||||
|
||||
typedef enum {
|
||||
ProcessVisibilityShown = 0,
|
||||
ProcessVisibilityHidden = 1,
|
||||
ProcessVisibilityShownOnCommunication = 2,
|
||||
ProcessVisibilityQuickLaunch = 3,
|
||||
} ProcessVisibility;
|
||||
|
||||
typedef enum {
|
||||
ProcessTypeApp = 0,
|
||||
ProcessTypeWatchface = 1,
|
||||
ProcessTypeWorker = 2,
|
||||
} ProcessType;
|
||||
|
||||
typedef enum ProcessAppRunLevel {
|
||||
ProcessAppRunLevelNormal = 0,
|
||||
ProcessAppRunLevelSystem = 1,
|
||||
ProcessAppRunLevelCritical = 2
|
||||
} ProcessAppRunLevel;
|
||||
|
||||
typedef enum {
|
||||
ProcessStorageBuiltin = 0,
|
||||
ProcessStorageFlash = 1,
|
||||
ProcessStorageResource = 2
|
||||
} ProcessStorage;
|
||||
|
||||
typedef enum {
|
||||
ProcessAppSDKType_System,
|
||||
ProcessAppSDKType_Legacy2x,
|
||||
ProcessAppSDKType_Legacy3x,
|
||||
ProcessAppSDKType_4x
|
||||
} ProcessAppSDKType;
|
||||
|
||||
//! This structure is used internally to describe the process. This struct here is actually a polymorphic base
|
||||
//! class, and can be casted to either \ref PebbleProcessMdSystem or \ref PebbleProcessMdFlash depending on the value
|
||||
//! of \ref is_flash_based. Clients shouldn't do this casting themselves though, and instead should use the
|
||||
//! process_metadata_get_* functions to safely retreive values from this struct.
|
||||
typedef struct PebbleProcessMd {
|
||||
Uuid uuid;
|
||||
|
||||
//! The address of the main function of the process. This will be inside the firmware for firmware processes and
|
||||
//! will be inside the process's RAM region for 3rd party processes.
|
||||
PebbleMain main_func;
|
||||
|
||||
//! The type of process
|
||||
ProcessType process_type;
|
||||
|
||||
// Flags
|
||||
ProcessVisibility visibility;
|
||||
|
||||
//! Where is the process stored?
|
||||
ProcessStorage process_storage;
|
||||
|
||||
//! Can this process call kernel functionality directly or does it need to go through syscalls?
|
||||
bool is_unprivileged;
|
||||
|
||||
//! Allow Javascript applications to access this process
|
||||
bool allow_js;
|
||||
|
||||
//! This process has a sister worker process in flash.
|
||||
bool has_worker;
|
||||
|
||||
//! Process is allowed to call RockyJS APIs
|
||||
bool is_rocky_app;
|
||||
|
||||
//! Bits of the sdk_platform as they were stored in the binary, or 0 if undefined
|
||||
uint16_t stored_sdk_platform;
|
||||
} PebbleProcessMd;
|
||||
|
||||
//! App metadata for apps that are built into the firmware.
|
||||
typedef struct PebbleProcessMdSystem {
|
||||
PebbleProcessMd common;
|
||||
|
||||
const char* name;
|
||||
|
||||
uint32_t icon_resource_id;
|
||||
|
||||
//! The level at which the process runs. Any processes that try to start but they have a lower level than what's
|
||||
//! set using the \ref app_manager_set_minimum_run_level() function will not be launched.
|
||||
ProcessAppRunLevel run_level;
|
||||
} PebbleProcessMdSystem;
|
||||
|
||||
//! Metadata for processes that are dynamically loaded from flash.
|
||||
typedef struct PebbleProcessMdFlash {
|
||||
PebbleProcessMd common;
|
||||
|
||||
char name[PROCESS_NAME_BYTES];
|
||||
|
||||
//! Size in bytes of the app region that is occupied when this app is loaded
|
||||
//! Used when sizing the app heap. For first-party apps, this value will
|
||||
//! always be zero.
|
||||
uint16_t size_bytes;
|
||||
|
||||
//! The version specified by the author for this process
|
||||
Version process_version;
|
||||
//! The version of the SDK this process was created with.
|
||||
Version sdk_version;
|
||||
|
||||
//! The bank this process will get it's code and data from. This field is only valid if the
|
||||
//! \ref process_storage is ProcessStorageFlash
|
||||
uint32_t code_bank_num;
|
||||
|
||||
//! The bank this app will get its resources from
|
||||
ResAppNum res_bank_num;
|
||||
|
||||
//! A version we can use to verify the resources in the resource bank on the filesystem are valid
|
||||
ResourceVersion res_version;
|
||||
|
||||
//! Build id of the application
|
||||
uint8_t build_id[BUILD_ID_EXPECTED_LEN];
|
||||
} PebbleProcessMdFlash;
|
||||
|
||||
|
||||
//! Metadata for processes that are dynamically loaded from a system resource.
|
||||
typedef struct {
|
||||
PebbleProcessMd common;
|
||||
|
||||
char name[PROCESS_NAME_BYTES];
|
||||
|
||||
//! Size in bytes of the app region that is occupied when this app is loaded
|
||||
//! Used when sizing the app heap.
|
||||
uint16_t size_bytes;
|
||||
|
||||
//! The resource number of the app binary
|
||||
uint32_t bin_resource_id;
|
||||
|
||||
} PebbleProcessMdResource;
|
||||
|
||||
|
||||
const char* process_metadata_get_name(const PebbleProcessMd *md);
|
||||
uint32_t process_metadata_get_size_bytes(const PebbleProcessMd *md);
|
||||
Version process_metadata_get_process_version(const PebbleProcessMd *md);
|
||||
Version process_metadata_get_sdk_version(const PebbleProcessMd *md);
|
||||
ProcessAppRunLevel process_metadata_get_run_level(const PebbleProcessMd *md);
|
||||
int process_metadata_get_code_bank_num(const PebbleProcessMd *md);
|
||||
int process_metadata_get_res_bank_num(const PebbleProcessMd *md);
|
||||
ResourceVersion process_metadata_get_res_version(const PebbleProcessMd *md);
|
||||
const uint8_t *process_metadata_get_build_id(const PebbleProcessMd *md);
|
||||
|
||||
//! @param[out] md
|
||||
void process_metadata_init_with_flash_header(PebbleProcessMdFlash *md,
|
||||
const PebbleProcessInfo *flash_header, int process_bank_num, PebbleTask task,
|
||||
uint8_t *build_id_buffer);
|
||||
|
||||
//! @param[out] md
|
||||
void process_metadata_init_with_resource_header(PebbleProcessMdResource *md,
|
||||
const PebbleProcessInfo *info, int bin_resource_id, PebbleTask task);
|
||||
|
||||
ProcessVisibility process_metadata_flags_visibility(PebbleProcessInfoFlags flags);
|
||||
|
||||
ProcessType process_metadata_flags_process_type(PebbleProcessInfoFlags flags, PebbleTask task);
|
||||
|
||||
bool process_metadata_flags_allow_js(PebbleProcessInfoFlags flags);
|
||||
|
||||
bool process_metadata_flags_has_worker(PebbleProcessInfoFlags flags);
|
||||
|
||||
bool process_metadata_flags_rocky_app(PebbleProcessInfoFlags flags);
|
||||
|
||||
uint16_t process_metadata_flags_stored_sdk_platform(PebbleProcessInfoFlags flags);
|
||||
|
||||
ProcessAppSDKType process_metadata_get_app_sdk_type(const PebbleProcessMd *md);
|
||||
|
||||
PlatformType process_metadata_get_app_sdk_platform(const PebbleProcessMd *md);
|
91
src/fw/process_management/process_heap.c
Normal file
91
src/fw/process_management/process_heap.c
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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_heap.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "process_management/pebble_process_info.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "system/passert.h"
|
||||
#include <util/heap.h>
|
||||
|
||||
static void prv_warn_on_double_free(void *ptr) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Double free detected on pointer <%p>", ptr);
|
||||
}
|
||||
|
||||
static void prv_croak_on_double_free(void *ptr) {
|
||||
// Always log regardless of CROAKing on unprivileged apps. We don't send the
|
||||
// croak message out over APP_LOG correctly so if we didn't do this developers
|
||||
// wouldn't see the croak reason.
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Double free detected on pointer <%p>", ptr);
|
||||
PBL_CROAK("Double free detected on pointer <%p>", ptr);
|
||||
}
|
||||
|
||||
static void prv_warn_on_heap_corruption(void *ptr) {
|
||||
// They're using 3.2 SDK or older, just let them off with a log message.
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Error: Heap corrupt around <%p>", ptr);
|
||||
}
|
||||
|
||||
static void prv_croak_on_heap_corruption(void *ptr) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Error: Heap corrupt around <%p>", ptr);
|
||||
PBL_CROAK("Error: Heap corrupt around <%p>", ptr);
|
||||
}
|
||||
|
||||
void process_heap_set_exception_handlers(Heap *heap,
|
||||
const PebbleProcessMd *app_md) {
|
||||
// prior to version 2.1 of the firmware (app sdk version 5.2), we never had
|
||||
// double free detection in our heap and we would just silently ignore someone
|
||||
// trying to free an invalid pointer. going forward we want to let our
|
||||
// developers know that this happened as firmly as possible. if an app is
|
||||
// compiled with the old sdk, yell at them through a log message so we don't
|
||||
// break any existing apps. if the app is compiled with a new sdk after we
|
||||
// made this change, just crash their app.
|
||||
static const Version old_style_double_free_handling_version = { 5, 1 };
|
||||
const Version app_sdk_version = process_metadata_get_sdk_version(app_md);
|
||||
if (version_compare(app_sdk_version,
|
||||
old_style_double_free_handling_version) <= 0) {
|
||||
heap_set_double_free_handler(heap, prv_warn_on_double_free);
|
||||
} else {
|
||||
heap_set_double_free_handler(heap, prv_croak_on_double_free);
|
||||
}
|
||||
// We try to detect heap corruption by looking at segment headers and
|
||||
// comparing the sizes of and prevSizes of consecutive blocks.
|
||||
//
|
||||
// This isn't bulletproof, but it's better than nothing. It's possible that
|
||||
// corruption is happening that doesn't affect the block headers (eg. use of a
|
||||
// dangling pointer), or that the overflow data simply matches what we wanted
|
||||
// to check anyways.
|
||||
//
|
||||
// There is no risk of this producing false positives, as any header
|
||||
// inconsistency is invalid.
|
||||
//
|
||||
// For some strange reason, some apps seem to be able to withstand heap
|
||||
// corruption. An example of this is overwriting the prevSize heap member with
|
||||
// a 0. An app will survive fine like this as long as we don't need to
|
||||
// traverse the heap in reverse.
|
||||
//
|
||||
// Since some apps can continue to run without issue, rather than tearing
|
||||
// everything down and create a bad user experience, let's hope that
|
||||
// developers read the logs and fix their apps.
|
||||
static const Version old_style_heap_corruption_version = { 5, 0x38 };
|
||||
|
||||
if (version_compare(app_sdk_version, old_style_heap_corruption_version) < 0) {
|
||||
// They're using 3.2 SDK or older, just let them off with a log message.
|
||||
heap_set_corruption_handler(heap, prv_warn_on_heap_corruption);
|
||||
} else {
|
||||
heap_set_corruption_handler(heap, prv_croak_on_heap_corruption);
|
||||
}
|
||||
}
|
25
src/fw/process_management/process_heap.h
Normal file
25
src/fw/process_management/process_heap.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
typedef struct Heap Heap;
|
||||
typedef struct PebbleProcessMd PebbleProcessMd;
|
||||
|
||||
//! Configure exception handlers (corruption and double-free) for
|
||||
//! app and worker heaps.
|
||||
void process_heap_set_exception_handlers(Heap *heap,
|
||||
const PebbleProcessMd *app_md);
|
40
src/fw/process_management/process_loader.h
Normal file
40
src/fw/process_management/process_loader.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kernel/pebble_tasks.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct MemorySegment MemorySegment;
|
||||
typedef struct PebbleProcessMd PebbleProcessMd;
|
||||
|
||||
//! Load the process image specified by app_md into memory.
|
||||
//!
|
||||
//! The memory that the process image is loaded into is split from the
|
||||
//! destination memory segment. The destination memory segment must
|
||||
//! already be zeroed out.
|
||||
//!
|
||||
//! Only the process' text, data and bss are loaded and split from the
|
||||
//! memory segment. It is the caller's responsibility to set up the
|
||||
//! process stack and heap.
|
||||
//!
|
||||
//! @return pointer to process's entry point function, or NULL if the
|
||||
//! process loading failed.
|
||||
void * process_loader_load(const PebbleProcessMd *app_md, PebbleTask task,
|
||||
MemorySegment *destination);
|
799
src/fw/process_management/process_manager.c
Normal file
799
src/fw/process_management/process_manager.c
Normal file
|
@ -0,0 +1,799 @@
|
|||
/*
|
||||
* 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 priviledged, 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 alled 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 proces 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 destory 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 processs 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);
|
||||
}
|
183
src/fw/process_management/process_manager.h
Normal file
183
src/fw/process_management/process_manager.h
Normal file
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "launch_config.h"
|
||||
|
||||
#include "applib/app_exit_reason.h"
|
||||
#include "applib/platform.h"
|
||||
#include "kernel/events.h"
|
||||
#include "process_management/app_install_types.h"
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "services/common/accel_manager.h"
|
||||
#include "services/common/compositor/compositor.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Used to identify invalid or system app when using app_bank calls
|
||||
#define INVALID_BANK_ID ~0
|
||||
#define SYSTEM_APP_BANK_ID (~0 - 1)
|
||||
|
||||
typedef enum ProcessRunState {
|
||||
ProcessRunState_Running,
|
||||
ProcessRunState_GracefullyClosing,
|
||||
ProcessRunState_ForceClosing
|
||||
} ProcessRunState;
|
||||
|
||||
typedef struct ProcessContext {
|
||||
//! The app metadata structure if this process represents a running application (NULL otherwise). Describes the
|
||||
//! static information about the currently running app.
|
||||
const PebbleProcessMd* app_md;
|
||||
|
||||
//! The app install id for this process if it represents an application.
|
||||
AppInstallId install_id;
|
||||
|
||||
//! The FreeRTOS task we're using the run the app. See xTaskHandle
|
||||
void* task_handle;
|
||||
|
||||
//! The address range the process was loaded into. It is used to
|
||||
//! convert physical addresses into relative addresses in order to
|
||||
//! assist developers when debugging their apps.
|
||||
void *load_start;
|
||||
void *load_end;
|
||||
|
||||
//! Queue used to send events to the process. The process will read PebbleEvents from here using
|
||||
//! sys_get_pebble_event.
|
||||
void* to_process_event_queue;
|
||||
|
||||
//! This bool indicates that we can safely stop and delete the process without causing
|
||||
//! any instability to the rest of the system. This is set in both graceful (process closing
|
||||
//! and returning) and non-graceful (force closes or crashes) cases. The task itself
|
||||
//! is the only task that's allowed to set this to true.
|
||||
volatile bool safe_to_kill;
|
||||
|
||||
//! Used to provide the application the method used to launch the application
|
||||
AppLaunchReason launch_reason;
|
||||
|
||||
//! The button information that launched the app used to provide the app this information
|
||||
ButtonId launch_button;
|
||||
|
||||
//! Used to allow the application to specify the reason it exited
|
||||
AppExitReason exit_reason;
|
||||
|
||||
//! Used to provide the application the wakeup_event that launched the application
|
||||
WakeupInfo wakeup_info;
|
||||
|
||||
//! What state the process is currently running in. Managed by the app_manager/worker_manager
|
||||
ProcessRunState closing_state;
|
||||
|
||||
//! Arguments passed to the process'. This is a pointer to a struct
|
||||
//! that is defined by the application.
|
||||
const void* args;
|
||||
|
||||
//! Pointer to a piece of data that we hold on behalf of the process. This makes it so
|
||||
//! our first party apps don't have to keep declaring their own statics to hold a pointer
|
||||
//! to a struct to represent their app data. Third party apps don't have this issue as their
|
||||
//! globals are loaded and unloaded when they start and stop, where the globals for our first
|
||||
//! party apps are always present. See app_state_get_user_data/app_state_set_user_data
|
||||
void* user_data;
|
||||
} ProcessContext;
|
||||
|
||||
typedef struct ProcessLaunchConfig {
|
||||
LaunchConfigCommon common;
|
||||
AppInstallId id;
|
||||
//! true if we're launching the worker for this app ID, false if we're launching the app.
|
||||
bool worker;
|
||||
//! true if the previous app should be closed forcefully, false otherwise.
|
||||
bool forcefully;
|
||||
} ProcessLaunchConfig;
|
||||
|
||||
|
||||
//! Init the process manager
|
||||
void process_manager_init(void);
|
||||
|
||||
//! Init the context variables
|
||||
void process_manager_init_context(ProcessContext* context,
|
||||
const PebbleProcessMd *app_md, const void *args);
|
||||
|
||||
//! Checks if the app refered to by the given AppInstallId is SDK compatible
|
||||
//! Returns true if it is compatible, false otherwise
|
||||
//! Shows an error dialog if it is not compatible
|
||||
bool process_manager_check_SDK_compatible(const AppInstallId id);
|
||||
|
||||
//! Request that a process be launched.
|
||||
//! @param id The app to launch. Note that this app may not be cached
|
||||
//! @param args The args to the app
|
||||
//! @param animation The animation that should be used to show the app
|
||||
//! @prama launch_reason The launch reason for the app starting
|
||||
void process_manager_launch_process(const ProcessLaunchConfig *config);
|
||||
|
||||
//! Close the given process. This is the highest level call which transfers control to the manager of that type of
|
||||
//! task. That manager will in turn call process_manager_make_process_safe_to_kill/process_manager_process_cleanup
|
||||
//! If it's an app, this will cause the next app in the system app state machine to automatically get launched.
|
||||
//! May only be called from the KernelMain task
|
||||
void process_manager_close_process(PebbleTask task, bool gracefully);
|
||||
|
||||
//! Called from the task itself, as it exits and reenters privileged mode
|
||||
NORETURN process_manager_task_exit(void);
|
||||
|
||||
//! Prod the given process into exiting. Returns true if it is safe to kill that task and clean it up using
|
||||
//! process_manager_process_cleanup
|
||||
bool process_manager_make_process_safe_to_kill(PebbleTask task, bool gracefully);
|
||||
|
||||
|
||||
//! Setup some required state for processes
|
||||
void process_manager_process_setup(PebbleTask task);
|
||||
|
||||
//! Kills the task and cleans it up. Must only be called if process_manager_make_process_safe_to_kill() returns
|
||||
//! true.
|
||||
void process_manager_process_cleanup(PebbleTask task);
|
||||
|
||||
//! Get the args for the current process
|
||||
const void* process_manager_get_current_process_args(void);
|
||||
|
||||
//! Send an event to the process' event loop
|
||||
bool process_manager_send_event_to_process(PebbleTask task, PebbleEvent* e);
|
||||
|
||||
//! Get the number of messages currently waiting to be processed by the process
|
||||
uint32_t process_manager_process_events_waiting(PebbleTask task);
|
||||
|
||||
//! Wrapper for \ref send_event_to_app to send callback events to the app
|
||||
void process_manager_send_callback_event_to_process(PebbleTask task, void (*callback)(void *), void *data);
|
||||
|
||||
//! Send a kill event for the given process to KernelMain
|
||||
void process_manager_put_kill_process_event(PebbleTask task, bool gracefully);
|
||||
|
||||
//! Right before a syscall function drops privilege, it calls this
|
||||
//! function to check whether the kernel is trying to kill the process.
|
||||
//! It offers us a place that we know is unprivileged for us to safely
|
||||
//! suspend the task and shut it down if it is misbehaving.
|
||||
void process_manager_handle_syscall_exit(void);
|
||||
|
||||
//! Return true if the current process was compiled with any 2.x SDK
|
||||
bool process_manager_compiled_with_legacy2_sdk(void);
|
||||
|
||||
//! Return true if the current process was compiled with any 3.x SDK
|
||||
bool process_manager_compiled_with_legacy3_sdk(void);
|
||||
|
||||
//! Returns platform the application was compiled against
|
||||
//! Use this to test, e.g. if app is Aplite while being executed on Basalt
|
||||
//! or Basalt while being executed on Emery
|
||||
PlatformType process_manager_current_platform(void);
|
||||
|
||||
//! Check if address is between lower_bound and the end of task's memory region.
|
||||
//! lower_bound is used by syscall_assert_userspace_buffer to ensure that
|
||||
//! address doesn't point into the stack frame of the executing syscall
|
||||
bool process_manager_is_address_in_region(PebbleTask task, const void *address,
|
||||
const void *lower_bound);
|
||||
|
||||
void *process_manager_address_to_offset(PebbleTask task, void *system_address);
|
33
src/fw/process_management/sdk_memory_limits.template.h
Normal file
33
src/fw/process_management/sdk_memory_limits.template.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
//! Memory limits for SDK applications
|
||||
//!
|
||||
//! These macros describe the memory limits imposed on SDK applications
|
||||
//! based on the major SDK version they were compiled against. The
|
||||
//! limits vary depending on both the SDK version and the platform.
|
||||
//!
|
||||
//! The APP_RAM_nX_SIZE macros describe the total amount of memory that
|
||||
//! an SDK app can use directly for stack, heap, text, data and bss.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define APP_RAM_SYSTEM_SIZE ((size_t)@APP_RAM_SYSTEM_SIZE@)
|
||||
#define APP_RAM_2X_SIZE ((size_t)@APP_RAM_2X_SIZE@)
|
||||
#define APP_RAM_3X_SIZE ((size_t)@APP_RAM_3X_SIZE@)
|
||||
#define APP_RAM_4X_SIZE ((size_t)@APP_RAM_4X_SIZE@)
|
22
src/fw/process_management/sdk_shims.c
Normal file
22
src/fw/process_management/sdk_shims.c
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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_state/app_state/app_state.h"
|
||||
|
||||
GContext* app_get_current_graphics_context(void) {
|
||||
return app_state_get_graphics_context();
|
||||
}
|
||||
|
35
src/fw/process_management/sdk_shims.h
Normal file
35
src/fw/process_management/sdk_shims.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
struct GContext;
|
||||
typedef struct GContext GContext;
|
||||
|
||||
//! @addtogroup Foundation
|
||||
//! @{
|
||||
//! @addtogroup App
|
||||
//! @{
|
||||
|
||||
//! Gets the graphics context that belongs to the caller of the function.
|
||||
//! @note Only use the returned GContext inside a drawing callback (`update_proc`)!
|
||||
//! @return The current graphics context
|
||||
//! @see \ref Drawing
|
||||
//! @see \ref LayerUpdateProc
|
||||
|
||||
GContext* app_get_current_graphics_context(void);
|
||||
|
||||
//! @} // end addtogroup App
|
||||
//! @} // end addtogroup Foundation
|
||||
|
22
src/fw/process_management/sdk_version.c
Normal file
22
src/fw/process_management/sdk_version.c
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 "sdk_version.h"
|
||||
|
||||
bool sdk_version_is_app_messaging_supported(const Version * const sdk_version) {
|
||||
return ((sdk_version->major == 3 && sdk_version->minor >= 1) ||
|
||||
sdk_version->major > 3);
|
||||
}
|
24
src/fw/process_management/sdk_version.h
Normal file
24
src/fw/process_management/sdk_version.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "pebble_process_info.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
//! Inspects the app metatdata whether the app supports app messaging.
|
||||
//! Only if this returns true, the .messaging_info field of PebbleAppHandlers can be used.
|
||||
//! @return true if the app is built with an SDK that supports app messaging or not
|
||||
bool sdk_version_is_app_messaging_supported(const Version * const sdk_version);
|
420
src/fw/process_management/worker_manager.c
Normal file
420
src/fw/process_management/worker_manager.c
Normal file
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* 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 "worker_manager.h"
|
||||
#include "process_loader.h"
|
||||
|
||||
// Pebble stuff
|
||||
#include "kernel/event_loop.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "kernel/util/segment.h"
|
||||
#include "kernel/util/task_init.h"
|
||||
#include "mcu/cache.h"
|
||||
#include "mcu/privilege.h"
|
||||
#include "os/tick.h"
|
||||
#include "popups/crashed_ui.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/process_heap.h"
|
||||
#include "process_state/worker_state/worker_state.h"
|
||||
#include "shell/prefs.h"
|
||||
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
// FreeRTOS stuff
|
||||
#include "FreeRTOS.h"
|
||||
#include "freertos_application.h"
|
||||
#include "task.h"
|
||||
#include "queue.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static const int MAX_TO_WORKER_EVENTS = 8;
|
||||
static ProcessContext s_worker_task_context;
|
||||
static QueueHandle_t s_to_worker_event_queue;
|
||||
|
||||
extern char __WORKER_RAM__[];
|
||||
extern char __WORKER_RAM_end__[];
|
||||
extern char __stack_guard_size__[];
|
||||
|
||||
//! Used by the "pebble gdb" command to locate the loaded worker in memory.
|
||||
void * volatile g_worker_load_address;
|
||||
|
||||
typedef struct NextWorker {
|
||||
const PebbleProcessMd *md;
|
||||
const void *args;
|
||||
} NextWorker;
|
||||
|
||||
static NextWorker s_next_worker;
|
||||
|
||||
static bool s_workers_enabled = true;
|
||||
|
||||
static AppInstallId s_last_worker_crashed_install_id;
|
||||
static time_t s_last_worker_crash_timestamp;
|
||||
static bool s_worker_crash_relaunches_disabled;
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
void worker_manager_init(void) {
|
||||
s_to_worker_event_queue = xQueueCreate(MAX_TO_WORKER_EVENTS, sizeof(PebbleEvent));
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// This is the wrapper function for the worker. It's not allowed to return as it's the top frame on the stack
|
||||
// created for the application.
|
||||
static void prv_worker_task_main(void *entry_point) {
|
||||
// Init worker state variables
|
||||
worker_state_init();
|
||||
task_init();
|
||||
|
||||
// about to start the worker in earnest. No longer safe to kill.
|
||||
s_worker_task_context.safe_to_kill = false;
|
||||
|
||||
// Enter unprivileged mode!
|
||||
const bool is_unprivileged = s_worker_task_context.app_md->is_unprivileged;
|
||||
if (is_unprivileged) {
|
||||
mcu_state_set_thread_privilege(false);
|
||||
}
|
||||
|
||||
const PebbleMain main_func = entry_point;
|
||||
main_func();
|
||||
|
||||
// Clean up after the worker. Remember to put only non-critical cleanup here,
|
||||
// as the worker may crash or otherwise misbehave. If something really needs to
|
||||
// be cleaned up, make it so the kernel can do it on the worker's behalf and put
|
||||
// the call at the bottom of prv_worker_cleanup.
|
||||
worker_state_deinit();
|
||||
|
||||
sys_exit();
|
||||
}
|
||||
|
||||
//! Heap locking function for our app heap. Our process heaps don't actually
|
||||
//! have to be locked because they're the sole property of the process and no
|
||||
//! other tasks should be touching it. All this function does is verify that
|
||||
//! this condition is met before continuing without locking.
|
||||
static void prv_heap_lock(void* unused) {
|
||||
PBL_ASSERT_TASK(PebbleTask_Worker);
|
||||
}
|
||||
|
||||
static size_t prv_get_worker_segment_size(const PebbleProcessMd *app_md) {
|
||||
// 12 KiB - 640 bytes workerlib static = 11648 bytes
|
||||
return 11648;
|
||||
}
|
||||
|
||||
static size_t prv_get_worker_stack_size(const PebbleProcessMd *app_md) {
|
||||
return 1400;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
bool worker_manager_launch_new_worker_with_args(const PebbleProcessMd *app_md, const void *args) {
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
|
||||
// Don't launch workers in recovery mode to reduce the chance of crashes
|
||||
#ifdef RECOVERY_FW
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// If workers are disabled, don't launch
|
||||
if (!s_workers_enabled) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Workers disabled");
|
||||
return false;
|
||||
}
|
||||
|
||||
// if we are trying to start another worker, then we want to enable relaunches on crashes.
|
||||
s_worker_crash_relaunches_disabled = false;
|
||||
|
||||
// If there is a different worker currently running, tell it to quit first. When it sees s_next_worker
|
||||
// set, it will call us again once it finishes closing the current worker
|
||||
if (s_worker_task_context.app_md != NULL && s_worker_task_context.app_md != app_md) {
|
||||
s_next_worker = (NextWorker) {
|
||||
.md = app_md,
|
||||
.args = args,
|
||||
};
|
||||
worker_manager_close_current_worker(true /*graceful*/);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Clear the next worker settings
|
||||
s_next_worker = (NextWorker) {};
|
||||
|
||||
// Error if a worker already launched
|
||||
if (pebble_task_get_handle_for_task(PebbleTask_Worker) != NULL) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Worker already launched");
|
||||
return false;
|
||||
}
|
||||
|
||||
process_manager_init_context(&s_worker_task_context, app_md, args);
|
||||
s_worker_task_context.to_process_event_queue = s_to_worker_event_queue;
|
||||
|
||||
// Set up the worker's memory and load the binary into it.
|
||||
const size_t worker_segment_size = prv_get_worker_segment_size(app_md);
|
||||
// The stack guard is counted as part of the app segment size...
|
||||
const size_t stack_guard_size = (uintptr_t)__stack_guard_size__;
|
||||
// ...and is carved out of the stack.
|
||||
const size_t stack_size =
|
||||
prv_get_worker_stack_size(app_md) - stack_guard_size;
|
||||
|
||||
MemorySegment worker_ram = { __WORKER_RAM__, __WORKER_RAM_end__ };
|
||||
memset((char *)worker_ram.start + stack_guard_size, 0,
|
||||
memory_segment_get_size(&worker_ram) - stack_guard_size);
|
||||
|
||||
MemorySegment worker_segment;
|
||||
PBL_ASSERTN(memory_segment_split(&worker_ram, &worker_segment,
|
||||
worker_segment_size));
|
||||
PBL_ASSERTN(memory_segment_split(&worker_segment, NULL, stack_guard_size));
|
||||
// No (accessible) memory segments can be placed between the top of WORKER_RAM
|
||||
// and the end of stack. Stacks always grow towards lower memory addresses, so
|
||||
// we want a stack overflow to touch the stack guard region before it begins
|
||||
// to clobber actual data. And syscalls assume that the stack is always at the
|
||||
// top of WORKER_RAM; violating this assumption will result in syscalls
|
||||
// sometimes failing when the worker hasn't done anything wrong.
|
||||
portSTACK_TYPE *stack = memory_segment_split(&worker_segment, NULL,
|
||||
stack_size);
|
||||
PBL_ASSERTN(stack);
|
||||
s_worker_task_context.load_start = worker_segment.start;
|
||||
g_worker_load_address = worker_segment.start;
|
||||
void *entry_point = process_loader_load(app_md, PebbleTask_Worker,
|
||||
&worker_segment);
|
||||
s_worker_task_context.load_end = worker_segment.start;
|
||||
if (!entry_point) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Tried to launch an invalid worker in bank %u!",
|
||||
process_metadata_get_code_bank_num(app_md));
|
||||
return false;
|
||||
}
|
||||
|
||||
// The rest of worker_ram is available for worker state to use as it sees fit.
|
||||
if (!worker_state_configure(&worker_ram)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "Worker state configuration failed");
|
||||
return false;
|
||||
}
|
||||
// The remaining space in worker_segment is assigned to the worker's
|
||||
// heap. Worker state needs to be configured before initializing the
|
||||
// heap as the WorkerState struct holds the worker heap's Heap object.
|
||||
Heap *worker_heap = worker_state_get_heap();
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Worker heap init %p %p",
|
||||
worker_segment.start, worker_segment.end);
|
||||
heap_init(worker_heap, worker_segment.start, worker_segment.end,
|
||||
/* enable_heap_fuzzing */ false);
|
||||
heap_set_lock_impl(worker_heap, (HeapLockImpl) {
|
||||
.lock_function = prv_heap_lock,
|
||||
});
|
||||
process_heap_set_exception_handlers(worker_heap, app_md);
|
||||
|
||||
// Init services required for this process before it starts to execute
|
||||
process_manager_process_setup(PebbleTask_Worker);
|
||||
|
||||
char task_name[configMAX_TASK_NAME_LEN];
|
||||
snprintf(task_name, sizeof(task_name), "Worker <%s>", process_metadata_get_name(s_worker_task_context.app_md));
|
||||
|
||||
TaskParameters_t task_params = {
|
||||
.pvTaskCode = prv_worker_task_main,
|
||||
.pcName = task_name,
|
||||
.usStackDepth = stack_size / sizeof(portSTACK_TYPE),
|
||||
.pvParameters = entry_point,
|
||||
.uxPriority = (tskIDLE_PRIORITY + 1) | portPRIVILEGE_BIT,
|
||||
.puxStackBuffer = stack,
|
||||
};
|
||||
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Starting %s", task_name);
|
||||
|
||||
pebble_task_create(PebbleTask_Worker, &task_params, &s_worker_task_context.task_handle);
|
||||
|
||||
// If no default yet, set as the default so that it can be relaunched upon system reset
|
||||
if (worker_manager_get_default_install_id() == INSTALL_ID_INVALID) {
|
||||
worker_manager_set_default_install_id(s_worker_task_context.install_id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Reset the data we're tracking for workers that crash
|
||||
static void prv_reset_last_worker_crashed_data(void) {
|
||||
// No need to reset s_last_worker_crash_timestamp because we always check the install id before
|
||||
// we check the timestamp
|
||||
s_last_worker_crashed_install_id = INSTALL_ID_INVALID;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Launch the next worker, if there is one
|
||||
void worker_manager_launch_next_worker(AppInstallId previous_worker_install_id) {
|
||||
// Is there another worker set to switch to?
|
||||
if (s_next_worker.md != NULL) {
|
||||
worker_manager_launch_new_worker_with_args(s_next_worker.md, s_next_worker.args);
|
||||
} else {
|
||||
// Do we have a default worker we should switch to that is different from the previous worker?
|
||||
AppInstallId default_id = worker_manager_get_default_install_id();
|
||||
if (default_id != INSTALL_ID_INVALID && default_id != previous_worker_install_id) {
|
||||
worker_manager_put_launch_worker_event(default_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_handle_remove_current_worker(void) {
|
||||
s_worker_crash_relaunches_disabled = true;
|
||||
worker_manager_close_current_worker(true);
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_close_current_worker(bool gracefully) {
|
||||
|
||||
// This method can be called as a result of receiving a PEBBLE_PROCESS_KILL_EVENT notification
|
||||
// from an app, telling us that it just finished it's deinit.
|
||||
|
||||
// Shouldn't be called from app. Use process_manager_put_kill_process_event() instead.
|
||||
PBL_ASSERT_TASK(PebbleTask_KernelMain);
|
||||
|
||||
// If no worker running, nothing to do
|
||||
if (!s_worker_task_context.app_md) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the process is safe to kill. If this method returns false, it will have set a timer
|
||||
// to post another KILL event in a few seconds, thus giving the process a chance to clean up.
|
||||
if (!process_manager_make_process_safe_to_kill(PebbleTask_Worker, gracefully)) {
|
||||
// Maybe next time...
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "Worker not ready to exit");
|
||||
return;
|
||||
}
|
||||
|
||||
// Save which worker we are exiting
|
||||
AppInstallId closing_worker_install_id = s_worker_task_context.install_id;
|
||||
|
||||
// Perform generic process cleanup
|
||||
process_manager_process_cleanup(PebbleTask_Worker);
|
||||
|
||||
// Notify the app install manager that we finally exited
|
||||
app_install_notify_worker_closed();
|
||||
|
||||
// If the worker was closed gracefully, launch any next/default worker and return
|
||||
if (gracefully) {
|
||||
// Reset the data tracking the last worker that crashed since the closing worker did not crash
|
||||
prv_reset_last_worker_crashed_data();
|
||||
worker_manager_launch_next_worker(closing_worker_install_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// We arrive here if the worker crashed...
|
||||
|
||||
// If the worker's app is in the foreground, close it
|
||||
if (closing_worker_install_id == app_manager_get_current_app_id()) {
|
||||
app_manager_force_quit_to_launcher();
|
||||
} else {
|
||||
const time_t current_time = rtc_get_time();
|
||||
const time_t WORKER_CRASH_RESET_TIMEOUT_SECONDS = 60;
|
||||
if ((closing_worker_install_id == s_last_worker_crashed_install_id) &&
|
||||
((current_time - s_last_worker_crash_timestamp) <= WORKER_CRASH_RESET_TIMEOUT_SECONDS)) {
|
||||
// Reset the data tracking the last worker that crashed since we are going to show crash UI
|
||||
prv_reset_last_worker_crashed_data();
|
||||
// Show the crash UI, which will ask the user if they want to launch the worker's app
|
||||
crashed_ui_show_worker_crash(closing_worker_install_id);
|
||||
} else {
|
||||
// Record that this worker crashed and what time it crashed
|
||||
s_last_worker_crashed_install_id = closing_worker_install_id;
|
||||
s_last_worker_crash_timestamp = current_time;
|
||||
// Silently restart the worker if we are allowing relaunches of crashed workers
|
||||
if (!s_worker_crash_relaunches_disabled) {
|
||||
worker_manager_put_launch_worker_event(closing_worker_install_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
const PebbleProcessMd* worker_manager_get_current_worker_md(void) {
|
||||
return s_worker_task_context.app_md;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
AppInstallId worker_manager_get_current_worker_id(void) {
|
||||
return s_worker_task_context.install_id;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
ProcessContext* worker_manager_get_task_context(void) {
|
||||
return &s_worker_task_context;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_put_launch_worker_event(AppInstallId id) {
|
||||
PBL_ASSERTN(id != INSTALL_ID_INVALID);
|
||||
|
||||
PebbleEvent e = {
|
||||
.type = PEBBLE_WORKER_LAUNCH_EVENT,
|
||||
.launch_app = {
|
||||
.id = id,
|
||||
},
|
||||
};
|
||||
|
||||
event_put(&e);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
AppInstallId worker_manager_get_default_install_id(void) {
|
||||
return worker_preferences_get_default_worker();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_set_default_install_id(AppInstallId id) {
|
||||
worker_preferences_set_default_worker(id);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_enable(void) {
|
||||
if (!s_workers_enabled) {
|
||||
s_workers_enabled = true;
|
||||
AppInstallId id = worker_manager_get_default_install_id();
|
||||
if (id != INSTALL_ID_INVALID) {
|
||||
worker_manager_put_launch_worker_event(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void worker_manager_disable(void) {
|
||||
if (s_workers_enabled) {
|
||||
s_workers_enabled = false;
|
||||
process_manager_put_kill_process_event(PebbleTask_Worker, true /*graceful*/);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void command_worker_kill(void) {
|
||||
process_manager_put_kill_process_event(PebbleTask_Worker, true /*graceful*/);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
DEFINE_SYSCALL(AppInstallId, sys_worker_manager_get_current_worker_id, void) {
|
||||
return worker_manager_get_current_worker_id();
|
||||
}
|
||||
|
58
src/fw/process_management/worker_manager.h
Normal file
58
src/fw/process_management/worker_manager.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pebble_process_md.h"
|
||||
#include "process_manager.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
// Worker management functions
|
||||
void worker_manager_init(void);
|
||||
|
||||
bool worker_manager_launch_new_worker_with_args(const PebbleProcessMd *app_md, const void *args);
|
||||
|
||||
void worker_manager_launch_next_worker(AppInstallId previous_worker_install_id);
|
||||
|
||||
//! Close the worker and never try to open it again until it is asked to.
|
||||
void worker_manager_handle_remove_current_worker(void);
|
||||
|
||||
void worker_manager_close_current_worker(bool gracefully);
|
||||
|
||||
const PebbleProcessMd* worker_manager_get_current_worker_md(void);
|
||||
|
||||
AppInstallId worker_manager_get_current_worker_id(void);
|
||||
|
||||
ProcessContext* worker_manager_get_task_context(void);
|
||||
|
||||
//! Exit the worker. Do some cleanup to make sure things close nicely.
|
||||
//! Called from the worker task
|
||||
NORETURN worker_task_exit(void);
|
||||
|
||||
void worker_manager_put_launch_worker_event(AppInstallId id);
|
||||
|
||||
//! Get the AppInstallId of the default worker to launch. Returns INSTALL_ID_INVALID if none set.
|
||||
AppInstallId worker_manager_get_default_install_id(void);
|
||||
|
||||
void worker_manager_set_default_install_id(AppInstallId id);
|
||||
|
||||
//! Can be called to enable worker support. Used by the watch-only mode for example
|
||||
//! to re-enable workers once the battery level recovers
|
||||
void worker_manager_enable(void);
|
||||
|
||||
//! Can be called to disable worker support. Used by the watch-only mode for example
|
||||
//! to disable workers when the battery is low. The current worker (if any) will be shut down.
|
||||
void worker_manager_disable(void);
|
Loading…
Add table
Add a link
Reference in a new issue