Files
pebble/src/fw/process_management/app_install_manager.c
2025-01-27 11:38:16 -08:00

894 lines
30 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "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(&reg_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 *) &reg_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();
}