/* * 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 "applib/app.h" #include "applib/graphics/bitblt.h" #include "applib/ui/ui.h" #include "applib/ui/window_private.h" #include "apps/prf_apps/mfg_accel_app.h" #include "apps/prf_apps/mfg_als_app.h" #include "apps/prf_apps/mfg_bt_sig_rf_app.h" #include "apps/prf_apps/mfg_btle_app.h" #include "apps/prf_apps/mfg_button_app.h" #include "apps/prf_apps/mfg_certification_app.h" #include "apps/prf_apps/mfg_display_app.h" #include "apps/prf_apps/mfg_hrm_app.h" #include "apps/prf_apps/mfg_runin_app.h" #include "apps/prf_apps/mfg_vibe_app.h" #include "kernel/event_loop.h" #include "kernel/pbl_malloc.h" #include "kernel/util/standby.h" #include "mfg/mfg_info.h" #include "mfg/mfg_serials.h" #include "process_management/app_manager.h" #include "process_state/app_state/app_state.h" #include "resource/resource_ids.auto.h" #include "services/common/bluetooth/local_id.h" #include "services/common/bluetooth/pairability.h" #include "system/reset.h" #include "util/size.h" #if PBL_ROUND #include "apps/prf_apps/mfg_display_calibration_app.h" #endif #include typedef struct { Window *window; SimpleMenuLayer *menu_layer; SimpleMenuSection menu_section; } MfgMenuAppData; static uint16_t s_menu_position = 0; #if MFG_INFO_RECORDS_TEST_RESULTS static GBitmap *s_menu_icons[2]; #define ICON_IDX_CHECK 0 #define ICON_IDX_X 1 #endif //! Callback to run from the kernel main task static void prv_launch_app_cb(void *data) { app_manager_launch_new_app(&(AppLaunchConfig) { .md = data }); } #if PBL_ROUND static void prv_select_calibrate_display(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_display_calibration_app_get_info()); } #endif static void prv_select_accel(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_accel_app_get_info()); } static void prv_select_button(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_button_app_get_info()); } static void prv_select_display(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_display_app_get_info()); } static void prv_select_runin(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_runin_app_get_info()); } static void prv_select_vibe(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_vibe_app_get_info()); } static void prv_select_als(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_als_app_get_info()); } static void prv_select_bt_sig_rf(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_bt_sig_rf_app_get_info()); } static void prv_select_hrm(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_hrm_app_get_info()); } static void prv_select_certification(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_certification_app_get_info()); } static void prv_select_btle(int index, void *context) { launcher_task_add_callback(prv_launch_app_cb, (void*) mfg_btle_app_get_info()); } static void prv_select_reset(int index, void *context) { system_reset(); } static void prv_select_shutdown(int index, void *context) { enter_standby(RebootReasonCode_ShutdownMenuItem); } static GBitmap * prv_get_icon_for_test(MfgTest test) { #if MFG_INFO_RECORDS_TEST_RESULTS const bool passed = mfg_info_get_test_result(test); if (passed) { return s_menu_icons[ICON_IDX_CHECK]; } return s_menu_icons[ICON_IDX_X]; #else return NULL; #endif } static void prv_load_icons(void) { #if MFG_INFO_RECORDS_TEST_RESULTS // The icons in resources are black boxes with either a white checkmark or X. // In order to make them look correct in the way we are using them, we want to // invert the icons so that they are black icon on a white background. // // To do this, load each resource temporarily and then create two new bitmaps. // Then bitblt the original resource into the new bitmap using GCompOpAssignInverted. const uint32_t icon_id[] = { RESOURCE_ID_ACTION_BAR_ICON_CHECK, RESOURCE_ID_ACTION_BAR_ICON_X }; for (unsigned i = 0; i < ARRAY_LENGTH(icon_id); ++i) { GBitmap tmp; gbitmap_init_with_resource(&tmp, icon_id[i]); GBitmap *icon = gbitmap_create_blank(tmp.bounds.size, tmp.info.format); bitblt_bitmap_into_bitmap(icon, &tmp, GPointZero, GCompOpAssignInverted, GColorBlack); s_menu_icons[i] = icon; gbitmap_deinit(&tmp); } #endif } //! @param[out] out_items static size_t prv_create_menu_items(SimpleMenuItem** out_menu_items) { prv_load_icons(); // Define a const blueprint on the stack. const SimpleMenuItem s_menu_items[] = { { .title = "BT Device Name" }, { .title = "Device Serial" }, #if PBL_ROUND { .title = "Calibrate Display", .callback = prv_select_calibrate_display }, #endif { .title = "Test Accel", .callback = prv_select_accel }, { .icon = prv_get_icon_for_test(MfgTest_Buttons), .title = "Test Buttons", .callback = prv_select_button }, { .icon = prv_get_icon_for_test(MfgTest_Display), .title = "Test Display", .callback = prv_select_display }, { .title = "Test Runin", .callback = prv_select_runin }, { .icon = prv_get_icon_for_test(MfgTest_Vibe), .title = "Test Vibe", .callback = prv_select_vibe }, { .icon = prv_get_icon_for_test(MfgTest_ALS), .title = "Test ALS", .callback = prv_select_als }, #if !PLATFORM_SILK { .title = "Test bt_sig_rf", .callback = prv_select_bt_sig_rf }, #endif #if CAPABILITY_HAS_BUILTIN_HRM { .title = "Test HRM", .callback = prv_select_hrm }, #endif #if BT_CONTROLLER_DA14681 { .title = "Test BTLE", .callback = prv_select_btle }, #endif { .title = "Certification", .callback = prv_select_certification }, { .title = "Reset", .callback = prv_select_reset }, { .title = "Shutdown", .callback = prv_select_shutdown } }; // Copy it into the heap so we can modify it. *out_menu_items = app_malloc(sizeof(s_menu_items)); memcpy(*out_menu_items, s_menu_items, sizeof(s_menu_items)); // Now we're going to modify the first two elements in the menu to include data available only // at runtime. If it was available at compile time we could have just shoved it in the // s_menu_items array but it's not. Note that we allocate a few buffers here that we never // bother freeing for simplicity. It's all on the app heap so it will automatically get cleaned // up on app exit. // Poke in the bluetooth name int buffer_size = BT_DEVICE_NAME_BUFFER_SIZE; char *bt_dev_name = app_malloc(buffer_size); bt_local_id_copy_device_name(bt_dev_name, false); (*out_menu_items)[0].subtitle = bt_dev_name; // Poke in the serial number buffer_size = MFG_SERIAL_NUMBER_SIZE + 1; char *device_serial = app_malloc(buffer_size); mfg_info_get_serialnumber(device_serial, buffer_size); (*out_menu_items)[1].subtitle = device_serial; // We've now populated out_menu_items with the correct data. Return the number of items by // looking at the original list of menu items. return ARRAY_LENGTH(s_menu_items); } static void prv_window_load(Window *window) { MfgMenuAppData *data = app_state_get_user_data(); Layer *window_layer = window_get_root_layer(data->window); GRect bounds = window_layer->bounds; #ifdef PLATFORM_SPALDING bounds.origin.x += 25; bounds.origin.y += 25; bounds.size.w -= 50; bounds.size.h -= 25; #endif SimpleMenuItem* menu_items; size_t num_items = prv_create_menu_items(&menu_items); data->menu_section = (SimpleMenuSection) { .num_items = num_items, .items = menu_items }; data->menu_layer = simple_menu_layer_create(bounds, data->window, &data->menu_section, 1, NULL); layer_add_child(window_layer, simple_menu_layer_get_layer(data->menu_layer)); // Set the menu layer back to it's previous highlight position simple_menu_layer_set_selected_index(data->menu_layer, s_menu_position, false); } static void s_main(void) { bt_pairability_use(); MfgMenuAppData *data = app_malloc_check(sizeof(MfgMenuAppData)); *data = (MfgMenuAppData){}; app_state_set_user_data(data); data->window = window_create(); window_init(data->window, ""); window_set_window_handlers(data->window, &(WindowHandlers) { .load = prv_window_load, }); window_set_overrides_back_button(data->window, true); window_set_fullscreen(data->window, true); app_window_stack_push(data->window, true /*animated*/); app_event_loop(); bt_pairability_release(); s_menu_position = simple_menu_layer_get_selected_index(data->menu_layer); } const PebbleProcessMd* mfg_menu_app_get_info(void) { static const PebbleProcessMdSystem s_app_info = { .common.main_func = &s_main, // UUID: ddfdf403-664e-47dd-a620-b1a14ce2b59b .common.uuid = { 0xdd, 0xfd, 0xf4, 0x03, 0x66, 0x4e, 0x47, 0xdd, 0xa6, 0x20, 0xb1, 0xa1, 0x4c, 0xe2, 0xb5, 0x9b }, .name = "MfgMenu", }; return (const PebbleProcessMd*) &s_app_info; }