pebble/src/fw/apps/prf_apps/mfg_menu_app.c

285 lines
9.5 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 "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 <string.h>
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;
}