mirror of
https://github.com/google/pebble.git
synced 2025-07-06 14:50:41 -04:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
335
src/fw/apps/system_apps/app_fetch_ui.c
Normal file
335
src/fw/apps/system_apps/app_fetch_ui.c
Normal file
|
@ -0,0 +1,335 @@
|
|||
/*
|
||||
* 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_fetch_ui.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "applib/app.h"
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "applib/ui/app_window_stack.h"
|
||||
#include "applib/ui/progress_window.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "drivers/battery.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/app_install_manager.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "process_management/worker_manager.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "services/common/i18n/i18n.h"
|
||||
#include "services/normal/app_fetch_endpoint.h"
|
||||
#include "services/normal/timeline/timeline_resources.h"
|
||||
#include "apps/system_apps/timeline/peek_layer.h"
|
||||
#include "shell/normal/watchface.h"
|
||||
#include "shell/shell.h"
|
||||
#include "shell/system_app_state_machine.h"
|
||||
#include "services/common/compositor/compositor_transitions.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "services/common/evented_timer.h"
|
||||
|
||||
#define FAIL_PAUSE_MS 1000
|
||||
#define SCROLL_OUT_MS 250
|
||||
#define BAR_HEIGHT 6
|
||||
#define BAR_WIDTH 80
|
||||
#define BAR_TO_TRANS_MS 160
|
||||
#define TRANS_TO_DOT_MS 90
|
||||
#define DOT_TRANSITION_RADIUS 13
|
||||
#define DOT_COMPOSITOR_RADIUS 7
|
||||
#define DOT_OFFSET 25
|
||||
#define UPDATE_INTERVAL 200
|
||||
#define UPDATE_AMOUNT 2
|
||||
#define FAILURE_PERCENT 15
|
||||
#define INITIAL_PERCENT 0
|
||||
|
||||
//! App data
|
||||
typedef struct {
|
||||
//! UI
|
||||
ProgressWindow window;
|
||||
|
||||
//! App fetch result
|
||||
AppFetchResult result;
|
||||
|
||||
//! Data
|
||||
AppInstallEntry install_entry;
|
||||
AppFetchUIArgs next_app_args;
|
||||
EventServiceInfo fetch_event_info;
|
||||
EventServiceInfo connect_event_info;
|
||||
|
||||
bool failed;
|
||||
} AppFetchUIData;
|
||||
|
||||
static void prv_set_progress(AppFetchUIData *data, int16_t progress) {
|
||||
progress_window_set_progress(&data->window, progress);
|
||||
}
|
||||
|
||||
// Launch the desired app
|
||||
static void prv_app_fetch_launch_app(AppFetchUIData *data) {
|
||||
// Let's launch the application we just fetched.
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "App Fetch: Putting launch event");
|
||||
|
||||
// if this was launched by the phone, it's probably a new install
|
||||
if ((data->next_app_args.common.reason == APP_LAUNCH_PHONE) &&
|
||||
!battery_is_usb_connected()) {
|
||||
vibes_short_pulse();
|
||||
}
|
||||
|
||||
// Allocate and inialize the data that would have been sent to the app originally before the
|
||||
// fetch request.
|
||||
PebbleLaunchAppEventExtended *ext = kernel_malloc_check(sizeof(PebbleLaunchAppEventExtended));
|
||||
*ext = (PebbleLaunchAppEventExtended) {
|
||||
.common = data->next_app_args.common,
|
||||
.wakeup = data->next_app_args.wakeup_info
|
||||
};
|
||||
#if PLATFORM_TINTIN
|
||||
ext->common.transition = compositor_app_slide_transition_get(true /* slide to right */);
|
||||
#else
|
||||
ext->common.transition = compositor_dot_transition_app_fetch_get();
|
||||
#endif
|
||||
if ((data->next_app_args.common.reason == APP_LAUNCH_WAKEUP) &&
|
||||
(data->next_app_args.common.args != NULL)) {
|
||||
ext->common.args = &data->next_app_args.wakeup_info;
|
||||
}
|
||||
|
||||
PebbleEvent launch_event = {
|
||||
.type = PEBBLE_APP_LAUNCH_EVENT,
|
||||
.launch_app = {
|
||||
.id = data->next_app_args.app_id,
|
||||
.data = ext
|
||||
}
|
||||
};
|
||||
|
||||
event_put(&launch_event);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// Animation Related Functions
|
||||
///////////////////////////////
|
||||
|
||||
static void prv_remote_comm_session_event_handler(PebbleEvent *event, void *context) {
|
||||
AppFetchUIData *data = app_state_get_user_data();
|
||||
if (event->bluetooth.comm_session_event.is_open &&
|
||||
event->bluetooth.comm_session_event.is_system) {
|
||||
progress_window_pop(&data->window);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_set_progress_failure(AppFetchUIData *data) {
|
||||
uint32_t icon;
|
||||
const char *message;
|
||||
switch (data->result) {
|
||||
case AppFetchResultNoBluetooth:
|
||||
icon = TIMELINE_RESOURCE_WATCH_DISCONNECTED;
|
||||
message = i18n_get("Not connected", data);
|
||||
// Subscribe to the BT remote app connect event
|
||||
data->connect_event_info = (EventServiceInfo) {
|
||||
.type = PEBBLE_COMM_SESSION_EVENT,
|
||||
.handler = prv_remote_comm_session_event_handler
|
||||
};
|
||||
event_service_client_subscribe(&data->connect_event_info);
|
||||
break;
|
||||
case AppFetchResultNoData:
|
||||
icon = TIMELINE_RESOURCE_CHECK_INTERNET_CONNECTION;
|
||||
#if PBL_ROUND
|
||||
// TODO PBL-28730: Fix peek layer so it does its own line wrapping
|
||||
message = i18n_get("No internet\nconnection", data);
|
||||
#else
|
||||
message = i18n_get("No internet connection", data);
|
||||
#endif
|
||||
break;
|
||||
case AppFetchResultIncompatibleJSFailure:
|
||||
// TODO: PBL-39752 make this a more expressive error message with a call to action
|
||||
icon = TIMELINE_RESOURCE_GENERIC_WARNING;
|
||||
message = i18n_get("Incompatible JS", data);
|
||||
break;
|
||||
case AppFetchResultGeneralFailure:
|
||||
case AppFetchResultUUIDInvalid:
|
||||
case AppFetchResultPutBytesFailure:
|
||||
case AppFetchResultTimeoutError:
|
||||
case AppFetchResultPhoneBusy:
|
||||
default:
|
||||
icon = TIMELINE_RESOURCE_GENERIC_WARNING;
|
||||
message = i18n_get("Failed", data);
|
||||
break;
|
||||
}
|
||||
|
||||
progress_window_set_result_failure(&data->window, icon, message,
|
||||
PROGRESS_WINDOW_DEFAULT_FAILURE_DELAY_MS);
|
||||
|
||||
if (!battery_is_usb_connected()) {
|
||||
vibes_short_pulse();
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_progress_window_finished(ProgressWindow *window, bool success, void *context) {
|
||||
AppFetchUIData *data = context;
|
||||
if (success) {
|
||||
prv_app_fetch_launch_app(data);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// Internal Helper Functions
|
||||
////////////////////////////
|
||||
|
||||
//! Used to clean up the application's data before exiting
|
||||
static void prv_app_fetch_cleanup(AppFetchUIData *data) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "App Fetch: prv_app_fetch_cleanup");
|
||||
event_service_client_unsubscribe(&data->fetch_event_info);
|
||||
event_service_client_unsubscribe(&data->connect_event_info);
|
||||
}
|
||||
|
||||
//! Used when the app fetch process has failed
|
||||
static void prv_app_fetch_failure(AppFetchUIData *data, uint8_t error_code) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "App Fetch: prv_app_fetch_failure: %d", error_code);
|
||||
|
||||
if (error_code == AppFetchResultUserCancelled) {
|
||||
app_window_stack_pop(true);
|
||||
}
|
||||
data->result = error_code;
|
||||
|
||||
if ((watchface_get_default_install_id() == data->install_entry.install_id) &&
|
||||
app_install_entry_is_watchface(&data->install_entry)) {
|
||||
// We failed to fetch a watchface and it was our default.
|
||||
// Invalidate it and it will be reassigned to one that exists next time around.
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Default watchface fetch failed, setting INVALID as default");
|
||||
watchface_set_default_install_id(INSTALL_ID_INVALID);
|
||||
} else if ((worker_manager_get_default_install_id() == data->install_entry.install_id) &&
|
||||
app_install_entry_has_worker(&data->install_entry)) {
|
||||
// We failed to fetch a worker and it was our default.
|
||||
// Invalidate it and it will be reassigned to one that is launched next.
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "Default worker fetch failed, setting INVALID as default");
|
||||
worker_manager_set_default_install_id(INSTALL_ID_INVALID);
|
||||
}
|
||||
|
||||
data->failed = true;
|
||||
prv_set_progress_failure(data);
|
||||
prv_app_fetch_cleanup(data);
|
||||
}
|
||||
|
||||
//! App Fetch handler. Used for keeping track of progress and cleanup events
|
||||
static void prv_app_fetch_event_handler(PebbleEvent *event, void *context) {
|
||||
AppFetchUIData *data = app_state_get_user_data();
|
||||
PebbleAppFetchEvent *af_event = (PebbleAppFetchEvent *) event;
|
||||
|
||||
// We have starting the App Fetch Process
|
||||
if (af_event->type == AppFetchEventTypeStart) {
|
||||
PBL_LOG(LOG_LEVEL_DEBUG, "App Fetch: Got the start event");
|
||||
|
||||
// We have received a new progress event
|
||||
} else if (af_event->type == AppFetchEventTypeProgress) {
|
||||
progress_window_set_progress(&data->window, af_event->progress_percent);
|
||||
|
||||
// We have finished the app fetch. Launching
|
||||
} else if (af_event->type == AppFetchEventTypeFinish) {
|
||||
progress_window_set_result_success(&data->window);
|
||||
prv_app_fetch_cleanup(data);
|
||||
|
||||
// We received an error. Fail
|
||||
} else if (af_event->type == AppFetchEventTypeError) {
|
||||
prv_app_fetch_failure(data, af_event->error_code);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use appropriate transitions to and from watchfaces or apps
|
||||
static void prv_click_handler(ClickRecognizerRef recognizer, Window *window) {
|
||||
AppFetchUIData *data = app_state_get_user_data();
|
||||
if (data->failed) {
|
||||
app_window_stack_pop(true);
|
||||
} else {
|
||||
app_fetch_cancel(data->install_entry.install_id);
|
||||
}
|
||||
}
|
||||
|
||||
static void config_provider(void *context) {
|
||||
window_single_click_subscribe(BUTTON_ID_BACK, (ClickHandler) prv_click_handler);
|
||||
window_single_click_subscribe(BUTTON_ID_UP, (ClickHandler) prv_click_handler);
|
||||
window_single_click_subscribe(BUTTON_ID_SELECT, (ClickHandler) prv_click_handler);
|
||||
window_single_click_subscribe(BUTTON_ID_BACK, (ClickHandler) prv_click_handler);
|
||||
}
|
||||
|
||||
static void handle_init(void) {
|
||||
AppFetchUIData* data = app_zalloc_check(sizeof(AppFetchUIData));
|
||||
app_state_set_user_data(data);
|
||||
|
||||
// get app args, copy them to app memory, and free the kernel buffer
|
||||
AppFetchUIArgs *temp_fetch_args =
|
||||
(AppFetchUIArgs *)process_manager_get_current_process_args();
|
||||
memcpy(&data->next_app_args, temp_fetch_args, sizeof(AppFetchUIArgs));
|
||||
kernel_free(temp_fetch_args);
|
||||
|
||||
// Create and set up window
|
||||
progress_window_init(&data->window);
|
||||
progress_window_set_callbacks(&data->window, (ProgressWindowCallbacks) {
|
||||
.finished = prv_progress_window_finished,
|
||||
}, data);
|
||||
window_set_click_config_provider((Window *)&data->window, config_provider);
|
||||
|
||||
// retrieve data about the AppInstallId given
|
||||
if (!app_install_get_entry_for_install_id(data->next_app_args.app_id, &data->install_entry)) {
|
||||
PBL_LOG(LOG_LEVEL_ERROR, "App Fetch: Error getting entry for id: %"PRIu32"",
|
||||
data->next_app_args.app_id);
|
||||
return;
|
||||
}
|
||||
|
||||
AppFetchError prev_error = app_fetch_get_previous_error();
|
||||
if ((prev_error.id == data->next_app_args.app_id) &&
|
||||
(prev_error.error != AppFetchResultSuccess)) {
|
||||
prv_app_fetch_failure(data, prev_error.error);
|
||||
prv_set_progress(data, FAILURE_PERCENT);
|
||||
}
|
||||
|
||||
// subscribe to PutBytes events
|
||||
data->fetch_event_info = (EventServiceInfo) {
|
||||
.type = PEBBLE_APP_FETCH_EVENT,
|
||||
.handler = prv_app_fetch_event_handler
|
||||
};
|
||||
event_service_client_subscribe(&data->fetch_event_info);
|
||||
|
||||
app_progress_window_push(&data->window);
|
||||
}
|
||||
|
||||
static void handle_deinit(void) {
|
||||
AppFetchUIData* data = app_state_get_user_data();
|
||||
prv_app_fetch_cleanup(data);
|
||||
progress_window_deinit(&data->window);
|
||||
app_free(data);
|
||||
i18n_free_all(data);
|
||||
}
|
||||
|
||||
static void s_main(void) {
|
||||
handle_init();
|
||||
|
||||
app_event_loop();
|
||||
|
||||
handle_deinit();
|
||||
}
|
||||
|
||||
const PebbleProcessMd *app_fetch_ui_get_app_info() {
|
||||
static const PebbleProcessMdSystem s_app_md = {
|
||||
.common = {
|
||||
.main_func = s_main,
|
||||
.visibility = ProcessVisibilityHidden,
|
||||
// UUID: 674271bc-f4fa-4536-97f3-8849a5ba75a4
|
||||
.uuid = {0x67, 0x42, 0x71, 0xbc, 0xf4, 0xfa, 0x45, 0x36,
|
||||
0x97, 0xf3, 0x88, 0x49, 0xa5, 0xba, 0x75, 0xa4},
|
||||
},
|
||||
.name = "App Fetch",
|
||||
};
|
||||
return (const PebbleProcessMd*) &s_app_md;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue