/* * 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 "watchface.h" #include "apps/system_app_ids.h" #include "apps/system_apps/launcher/launcher_app.h" #include "apps/system_apps/settings/settings_quick_launch.h" #include "apps/system_apps/settings/settings_quick_launch_app_menu.h" #include "apps/system_apps/settings/settings_quick_launch_setup_menu.h" #include "apps/system_apps/timeline/timeline.h" #include "apps/watch/low_power/low_power_face.h" #include "kernel/event_loop.h" #include "kernel/low_power.h" #include "kernel/ui/modals/modal_manager.h" #include "popups/timeline/peek.h" #include "process_management/app_manager.h" #include "process_management/pebble_process_md.h" #include "services/common/analytics/analytics.h" #include "services/common/compositor/compositor_transitions.h" #include "services/normal/notifications/do_not_disturb.h" #include "system/logging.h" #include "system/passert.h" #define QUICK_LAUNCH_HOLD_MS (400) static ClickManager s_click_manager; static bool prv_should_ignore_button_click(void) { if (app_manager_get_task_context()->closing_state != ProcessRunState_Running) { // Ignore if the app is not running (such as if it is in the process of closing) return true; } if (low_power_is_active()) { // If we're in low power mode we dont allow any interaction return true; } return false; } static void prv_launch_app_via_button(AppLaunchEventConfig *config, ClickRecognizerRef recognizer) { config->common.button = click_recognizer_get_button_id(recognizer); app_manager_put_launch_app_event(config); } static void prv_quick_launch_handler(ClickRecognizerRef recognizer, void *data) { ButtonId button = click_recognizer_get_button_id(recognizer); if (!quick_launch_is_enabled(button)) { return; } AppInstallId app_id = quick_launch_get_app(button); if (app_id == INSTALL_ID_INVALID) { app_id = app_install_get_id_for_uuid(&quick_launch_setup_get_app_info()->uuid); } prv_launch_app_via_button(&(AppLaunchEventConfig) { .id = app_id, .common.reason = APP_LAUNCH_QUICK_LAUNCH, }, recognizer); } static void prv_launch_timeline(ClickRecognizerRef recognizer, void *data) { static TimelineArgs s_timeline_args; const bool is_up = (click_recognizer_get_button_id(recognizer) == BUTTON_ID_UP); if (is_up) { PBL_LOG(LOG_LEVEL_DEBUG, "Launching timeline in past mode."); s_timeline_args.direction = TimelineIterDirectionPast; analytics_inc(ANALYTICS_DEVICE_METRIC_TIMELINE_PAST_LAUNCH_COUNT, AnalyticsClient_System); } else { PBL_LOG(LOG_LEVEL_DEBUG, "Launching timeline in future mode."); s_timeline_args.direction = TimelineIterDirectionFuture; analytics_inc(ANALYTICS_DEVICE_METRIC_TIMELINE_FUTURE_LAUNCH_COUNT, AnalyticsClient_System); } s_timeline_args.launch_into_pin = true; s_timeline_args.stay_in_list_view = true; timeline_peek_get_item_id(&s_timeline_args.pin_id); const CompositorTransition *animation = NULL; const bool is_future = (s_timeline_args.direction == TimelineIterDirectionFuture); const bool timeline_is_destination = true; #if PBL_ROUND animation = compositor_dot_transition_timeline_get(is_future, timeline_is_destination); #else const bool jump = (!uuid_is_invalid(&s_timeline_args.pin_id) && !timeline_peek_is_first_event()); animation = jump ? compositor_peek_transition_timeline_get() : compositor_slide_transition_timeline_get(is_future, timeline_is_destination, timeline_peek_is_future_empty()); #endif prv_launch_app_via_button(&(AppLaunchEventConfig) { .id = APP_ID_TIMELINE, .common.args = &s_timeline_args, .common.transition = animation, }, recognizer); } static void prv_configure_click_handler(ButtonId button_id, ClickHandler single_click_handler) { ClickConfig *cfg = &s_click_manager.recognizers[button_id].config; cfg->long_click.delay_ms = QUICK_LAUNCH_HOLD_MS; cfg->long_click.handler = prv_quick_launch_handler; cfg->click.handler = single_click_handler; } static void prv_launch_launcher_app(ClickRecognizerRef recognizer, void *data) { static const LauncherMenuArgs s_launcher_args = { .reset_scroll = true }; prv_launch_app_via_button(&(AppLaunchEventConfig) { .id = APP_ID_LAUNCHER_MENU, .common.args = &s_launcher_args, }, recognizer); } #if CAPABILITY_HAS_CORE_NAVIGATION4 static void prv_launch_health_app(ClickRecognizerRef recognizer, void *data) { prv_launch_app_via_button(&(AppLaunchEventConfig) { .id = APP_ID_HEALTH_APP, }, recognizer); } #endif // CAPABILITY_HAS_CORE_NAVIGATION4 static ClickHandler prv_get_up_click_handler(void) { #if CAPABILITY_HAS_CORE_NAVIGATION4 return prv_launch_health_app; #else return prv_launch_timeline; #endif // CAPABILITY_HAS_CORE_NAVIGATION4 } static void prv_dismiss_timeline_peek(ClickRecognizerRef recognizer, void *data) { timeline_peek_dismiss(); } static void prv_watchface_configure_click_handlers(void) { prv_configure_click_handler(BUTTON_ID_UP, prv_get_up_click_handler()); prv_configure_click_handler(BUTTON_ID_DOWN, prv_launch_timeline); prv_configure_click_handler(BUTTON_ID_SELECT, prv_launch_launcher_app); prv_configure_click_handler(BUTTON_ID_BACK, prv_dismiss_timeline_peek); } void watchface_init(void) { click_manager_init(&s_click_manager); prv_watchface_configure_click_handlers(); } void watchface_handle_button_event(PebbleEvent *e) { if (prv_should_ignore_button_click()) { return; } switch (e->type) { case PEBBLE_BUTTON_DOWN_EVENT: click_recognizer_handle_button_down(&s_click_manager.recognizers[e->button.button_id]); break; case PEBBLE_BUTTON_UP_EVENT: click_recognizer_handle_button_up(&s_click_manager.recognizers[e->button.button_id]); break; default: PBL_CROAK("Invalid event type: %u", e->type); break; } } static void prv_watchface_launch_low_power(void) { PBL_LOG(LOG_LEVEL_DEBUG, "Switching default watchface to low_power_mode watchface"); app_manager_put_launch_app_event(&(AppLaunchEventConfig) { .id = APP_ID_LOW_POWER_FACE, }); } void watchface_launch_default(const CompositorTransition *animation) { app_manager_put_launch_app_event(&(AppLaunchEventConfig) { .id = watchface_get_default_install_id(), .common.transition = animation, }); } static void kernel_callback_watchface_launch(void* data) { watchface_launch_default(NULL); } void command_watch(void) { launcher_task_add_callback(kernel_callback_watchface_launch, NULL); } void watchface_start_low_power(void) { app_manager_set_minimum_run_level(ProcessAppRunLevelNormal); prv_watchface_launch_low_power(); } void watchface_reset_click_manager(void) { click_manager_reset(&s_click_manager); }