pebble/src/fw/popups/bluetooth_pairing_ui.c
Josh Soref 2df7d018dd spelling: additional
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-28 15:04:32 -05:00

695 lines
26 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.
*/
#define FILE_LOG_COLOR LOG_COLOR_BLUE
#include "bluetooth_pairing_ui.h"
#include "applib/fonts/fonts.h"
#include "applib/ui/kino/kino_layer.h"
#include "applib/ui/kino/kino_reel.h"
#include "applib/ui/ui.h"
#include "applib/ui/window_private.h"
#include "applib/ui/window_stack.h"
#include "comm/bt_lock.h"
#include "kernel/events.h"
#include "kernel/event_loop.h"
#include "kernel/pbl_malloc.h"
#include "kernel/ui/modals/modal_manager.h"
#include "kernel/ui/system_icons.h"
#include "resource/resource_ids.auto.h"
#include "services/common/i18n/i18n.h"
#include "services/common/light.h"
#include "services/common/new_timer/new_timer.h"
#include "services/common/system_task.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/size.h"
#include <bluetooth/id.h>
#include <bluetooth/pairing_confirm.h>
#include <stdio.h>
#include <string.h>
#define CODE_BUF_SIZE 16
#define MAX_PAIR_STR_LEN 16
typedef enum {
BTPairingUIStateAwaitingUserConfirmation, // It's possible to go from here straight to Failed
BTPairingUIStateAwaitingResult,
BTPairingUIStateSuccess,
BTPairingUIStateFailed,
} BTPairingUIState;
typedef struct BTPairingUIData {
Window window;
KinoLayer kino_layer;
KinoReel *reel;
GBitmap *approve_bitmap;
GBitmap *decline_bitmap;
ActionBarLayer action_bar_layer;
Layer info_text_mask_layer;
PropertyAnimation *info_text_out_animation;
PropertyAnimation *info_text_in_animation;
// The info text layers store show the text that prompts the user to pair
TextLayer info_text_layer;
char info_text_layer_buffer[MAX_PAIR_STR_LEN];
#ifdef RECOVERY_FW
GRect pair_text_area;
GRect above_pair_text_area;
TextLayer info_text_layer2;
char info_text_layer2_buffer[MAX_PAIR_STR_LEN];
int num_strings_shown;
int translated_str_idx;
#endif
TextLayer device_name_text_layer;
char device_name_layer_buffer[BT_DEVICE_NAME_BUFFER_SIZE];
TextLayer code_text_layer;
char code_text_layer_buffer[CODE_BUF_SIZE];
TimerID timer;
BTPairingUIState ui_state;
const PairingUserConfirmationCtx *ctx;
} BTPairingUIData;
//! This pointer and the data it points to should only be accessed from KernelMain
static BTPairingUIData *s_data_ptr = NULL;
static void prv_handle_pairing_complete(bool success);
#ifdef RECOVERY_FW // PRF -- animate through a few hard-coded text strings for "Pair?"
static void prv_animate_info_text(BTPairingUIData *data);
static void prv_info_text_animation_stopped(Animation *anim, bool finished, void *context) {
if (!s_data_ptr) {
return;
}
// Reset the text box positions
layer_set_frame(&s_data_ptr->info_text_layer.layer, &s_data_ptr->pair_text_area);
layer_set_frame(&s_data_ptr->info_text_layer2.layer, &s_data_ptr->above_pair_text_area);
if (s_data_ptr->ui_state == BTPairingUIStateAwaitingUserConfirmation) {
// Reschedule animations
prv_animate_info_text(s_data_ptr);
}
}
static void prv_cleanup_prf_animations(BTPairingUIData *data) {
animation_unschedule(property_animation_get_animation(data->info_text_in_animation));
animation_unschedule(property_animation_get_animation(data->info_text_out_animation));
layer_set_hidden(&data->info_text_layer2.layer, true);
}
typedef struct {
const char *string;
const char *font_key;
} Translation;
static void prv_update_text_layer_with_translation(TextLayer *text_layer,
const Translation *translation) {
text_layer_set_text(text_layer, translation->string);
text_layer_set_font(text_layer, fonts_get_system_font(translation->font_key));
}
static void prv_update_prf_info_text_layers_text(BTPairingUIData *data) {
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
const char *font_key_default = FONT_KEY_GOTHIC_28_BOLD;
const char *font_key_japanese = FONT_KEY_MINCHO_24_PAIR;
#else
const char *font_key_default = FONT_KEY_GOTHIC_24_BOLD;
const char *font_key_japanese = FONT_KEY_MINCHO_20_PAIR;
#endif
const Translation english_translation = { "Pair?", font_key_default };
const Translation translations[] = {
{ "Koppeln?", font_key_default }, // German
{ "Jumeler?", font_key_default }, // French
{ "¿Enlazar?", font_key_default }, // Spanish
{ "Associare?", font_key_default }, // Italian
{ "Emparelhar?", font_key_default }, // Portuguese
{ "ペアリング", font_key_japanese }, // Japanese
{ "配对", font_key_default }, // Chinese (tradditional?)
{ "配對", font_key_default } // Chinese (simplified?)
};
// The strings should be displayed in the following pattern:
// english, translated, translated, english, translated, translated, ...
if (data->num_strings_shown % 3 == 0) {
prv_update_text_layer_with_translation(&data->info_text_layer, &english_translation);
} else {
prv_update_text_layer_with_translation(&data->info_text_layer,
&translations[data->translated_str_idx]);
data->translated_str_idx = (data->translated_str_idx + 1) % ARRAY_LENGTH(translations);
}
if ((data->num_strings_shown + 1) % 3 == 0) {
prv_update_text_layer_with_translation(&data->info_text_layer2, &english_translation);
} else {
prv_update_text_layer_with_translation(&data->info_text_layer2,
&translations[data->translated_str_idx]);
}
data->num_strings_shown++;
}
static void prv_animate_info_text(BTPairingUIData *data) {
prv_update_prf_info_text_layers_text(data);
animation_schedule(property_animation_get_animation(data->info_text_in_animation));
animation_schedule(property_animation_get_animation(data->info_text_out_animation));
}
static void prv_add_prf_layers(GRect pair_text_area, BTPairingUIData *data) {
GRect below_pair_text = GRect(0, 38, pair_text_area.size.w, 30);
GRect above_pair_text = GRect(0, -34, pair_text_area.size.w, 30);
data->pair_text_area = pair_text_area;
data->above_pair_text_area = above_pair_text;
TextLayer *info_text_layer2 = &data->info_text_layer2;
text_layer_init_with_parameters(info_text_layer2,
&above_pair_text,
data->info_text_layer2_buffer,
fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
GColorBlack, GColorClear, GTextAlignmentCenter,
GTextOverflowModeTrailingEllipsis);
layer_add_child(&data->info_text_mask_layer, &info_text_layer2->layer);
// The order in which the languages are shown (see prv_update_prf_info_text_layers_text()) means
// that in order to see each translated language twice we must show 15 total strings.
// The Bluetooth SPP popup will timeout in 30 seconds so each animation + delay = 30/15 = 2s
const int animation_duration_ms = 300;
const int animation_delay_ms = 1700;
// This is the text that is currently not visible and animates into view
data->info_text_in_animation =
property_animation_create_layer_frame(&data->info_text_layer2.layer,
&above_pair_text, &pair_text_area);
PBL_ASSERTN(data->info_text_in_animation);
Animation *animation = property_animation_get_animation(data->info_text_in_animation);
animation_set_auto_destroy(animation, false);
animation_set_duration(animation, animation_duration_ms);
animation_set_delay(animation, animation_delay_ms);
// This is the text that is currently visible and animates out of view
data->info_text_out_animation =
property_animation_create_layer_frame(&data->info_text_layer.layer,
&pair_text_area, &below_pair_text);
PBL_ASSERTN(data->info_text_out_animation);
animation = property_animation_get_animation(data->info_text_out_animation);
animation_set_auto_destroy(animation, false);
animation_set_duration(animation, animation_duration_ms);
animation_set_delay(animation, animation_delay_ms);
// We only need a stop handler for one of the animations as they should finish at the same time
AnimationHandlers handlers = {
.stopped = prv_info_text_animation_stopped,
};
animation_set_handlers(animation, handlers, NULL);
}
static void prv_initialize_info_text(BTPairingUIData *data) {
prv_animate_info_text(data);
}
static void prv_deinitialize_info_text(BTPairingUIData *data) {
}
#else // Normal FW -- use i18n text for "Pair?"
static void prv_cleanup_prf_animations(BTPairingUIData *data) {
}
static void prv_add_prf_layers(GRect pair_text_area, BTPairingUIData *data) {
}
static void prv_initialize_info_text(BTPairingUIData *data) {
strncpy(data->info_text_layer_buffer, (char *)i18n_get("Pair?", data), MAX_PAIR_STR_LEN);
}
static void prv_deinitialize_info_text(BTPairingUIData *data) {
i18n_free_all(data);
}
#endif
static uint32_t prv_resource_id_for_state(BTPairingUIState state) {
switch (state) {
case BTPairingUIStateAwaitingUserConfirmation:
return RESOURCE_ID_BT_PAIR_CONFIRMATION;
case BTPairingUIStateAwaitingResult:
return RESOURCE_ID_BT_PAIR_APPROVE_ON_PHONE;
case BTPairingUIStateSuccess:
return RESOURCE_ID_BT_PAIR_SUCCESS;
case BTPairingUIStateFailed:
return RESOURCE_ID_BT_PAIR_FAILURE;
default:
WTF;
}
}
static void prv_adjust_background_frame_for_state(BTPairingUIData *data) {
GAlign alignment;
const int16_t width_of_sidebar = data->action_bar_layer.layer.frame.size.w;
const int16_t window_width = data->window.layer.bounds.size.w;
const int16_t config_width = window_width - width_of_sidebar + 10;
int16_t x_offset, y_offset, width;
switch (data->ui_state) {
case BTPairingUIStateAwaitingUserConfirmation:
alignment = GAlignTopLeft;
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
x_offset = 39;
y_offset = 85;
#else
x_offset = PBL_IF_RECT_ELSE(10, 31);
y_offset = PBL_IF_RECT_ELSE(44, 46);
#endif
width = config_width;
break;
case BTPairingUIStateAwaitingResult:
alignment = GAlignLeft;
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
x_offset = 76;
y_offset = 30;
#else
x_offset = PBL_IF_RECT_ELSE(49, 67);
y_offset = PBL_IF_RECT_ELSE(22, 25);
#endif
width = window_width;
break;
case BTPairingUIStateFailed:
case BTPairingUIStateSuccess:
alignment = GAlignTop;
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
x_offset = 0;
y_offset = 59;
#else
x_offset = 2;
y_offset = PBL_IF_RECT_ELSE(30, 36);
#endif
width = window_width;
break;
default:
WTF;
}
GRect kino_area;
kino_area = GRect(x_offset, y_offset, width, data->window.layer.bounds.size.h);
kino_layer_set_alignment(&data->kino_layer, alignment);
layer_set_frame(&data->kino_layer.layer, &kino_area);
kino_layer_set_reel_with_resource(&data->kino_layer, prv_resource_id_for_state(data->ui_state));
}
static void prv_send_response(bool is_confirmed) {
if (!s_data_ptr) {
return;
}
bt_lock();
bt_driver_pairing_confirm(s_data_ptr->ctx, is_confirmed);
bt_unlock();
}
static bool prv_has_device_name(BTPairingUIData *data) {
return (strlen(data->device_name_layer_buffer) != 0);
}
static bool prv_has_confirmation_token(BTPairingUIData *data) {
return (strlen(data->code_text_layer_buffer) != 0);
}
static void prv_exit_awaiting_user_confirmation(BTPairingUIData *data) {
// Remove UI components that are not needed any more after the user input confirmation screen:
prv_cleanup_prf_animations(data);
layer_set_hidden(&data->info_text_layer.layer, true);
if (prv_has_device_name(data)) {
layer_remove_from_parent(&data->device_name_text_layer.layer);
}
if (prv_has_confirmation_token(data)) {
layer_remove_from_parent(&data->code_text_layer.layer);
}
// Disable all buttons in this screen:
action_bar_layer_remove_from_window(&data->action_bar_layer);
action_bar_layer_set_click_config_provider(&data->action_bar_layer, NULL);
}
static void prv_confirm_click_handler(ClickRecognizerRef recognizer, void *ctx) {
Window *window = (Window *) ctx;
BTPairingUIData *data = window_get_user_data(window);
PBL_ASSERTN(data->ui_state == BTPairingUIStateAwaitingUserConfirmation);
prv_exit_awaiting_user_confirmation(data);
data->ui_state = BTPairingUIStateAwaitingResult;
prv_send_response(true /* is_confirmed */);
prv_adjust_background_frame_for_state(data);
}
static void prv_decline_click_handler(ClickRecognizerRef recognizer, void *ctx) {
Window *window = (Window *) ctx;
BTPairingUIData *data = window_get_user_data(window);
PBL_ASSERTN(data->ui_state == BTPairingUIStateAwaitingUserConfirmation);
prv_send_response(false /* is_confirmed */);
// Not updating ui_state, the handler is capable of dealing with transitioning from
// BTPairingUIStateAwaitingUserConfirmation directly to BTPairingUIStateFailed
prv_handle_pairing_complete(false /* success */);
}
static void prv_user_confirmation_click_config_provider(void *unused) {
window_single_click_subscribe(BUTTON_ID_UP, prv_confirm_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, prv_decline_click_handler);
}
static void prv_window_load(Window *window) {
BTPairingUIData *data = window_get_user_data(window);
window_set_background_color(&data->window, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite));
const int32_t width_of_action_bar_with_padding = ACTION_BAR_WIDTH + PBL_IF_RECT_ELSE(2, -4);
const int32_t width = window->layer.bounds.size.w - width_of_action_bar_with_padding;
const int32_t x_offset = PBL_IF_RECT_ELSE(0, 22);
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
const int32_t info_text_y_offset = 36;
#else
const int32_t info_text_y_offset = PBL_IF_RECT_ELSE(10, 12);
#endif
KinoLayer *kino_layer = &data->kino_layer;
kino_layer_init(kino_layer, &window->layer.bounds);
layer_add_child(&window->layer, &kino_layer->layer);
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
GRect pair_text_area = GRect(0, -2, width, 44);
#else
GRect pair_text_area = GRect(0, -2, width, 30);
#endif
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
layer_set_frame(&data->info_text_mask_layer, &GRect(x_offset, info_text_y_offset, width, 30));
#else
layer_set_frame(&data->info_text_mask_layer, &GRect(x_offset, info_text_y_offset, width, 26));
#endif
layer_set_clips(&data->info_text_mask_layer, true);
layer_add_child(&window->layer, &data->info_text_mask_layer);
TextLayer *info_text_layer = &data->info_text_layer;
text_layer_init_with_parameters(info_text_layer,
&pair_text_area,
data->info_text_layer_buffer,
#if PLATFORM_ROBERT || PLATFORM_CALCULUS
fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD),
#else
fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
#endif
GColorBlack, GColorClear, GTextAlignmentCenter,
GTextOverflowModeTrailingEllipsis);
layer_add_child(&data->info_text_mask_layer, &info_text_layer->layer);
ActionBarLayer *action_bar_layer = &data->action_bar_layer;
action_bar_layer_init(action_bar_layer);
action_bar_layer_add_to_window(action_bar_layer, window);
data->approve_bitmap = gbitmap_create_with_resource(RESOURCE_ID_ACTION_BAR_ICON_CHECK);
data->decline_bitmap = gbitmap_create_with_resource(RESOURCE_ID_ACTION_BAR_ICON_X);
action_bar_layer_set_click_config_provider(action_bar_layer,
prv_user_confirmation_click_config_provider);
action_bar_layer_set_icon(action_bar_layer, BUTTON_ID_UP, data->approve_bitmap);
action_bar_layer_set_icon(action_bar_layer, BUTTON_ID_DOWN, data->decline_bitmap);
action_bar_layer_set_context(action_bar_layer, data);
prv_add_prf_layers(pair_text_area, data);
const int16_t y_offset = PBL_IF_RECT_ELSE(0, 2);
// Device name:
if (prv_has_device_name(data)) {
TextLayer *device_name_layer = &data->device_name_text_layer;
text_layer_init_with_parameters(device_name_layer,
&GRect(x_offset, 122 + y_offset, width - x_offset, 30),
data->device_name_layer_buffer,
fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD),
GColorBlack, GColorClear, GTextAlignmentCenter,
GTextOverflowModeTrailingEllipsis);
layer_add_child(&window->layer, &device_name_layer->layer);
}
// Confirmation token:
if (prv_has_confirmation_token(data)) {
TextLayer *code_text_layer = &data->code_text_layer;
text_layer_init_with_parameters(code_text_layer,
&GRect(x_offset, 148 + y_offset, width, 30),
data->code_text_layer_buffer,
fonts_get_system_font(FONT_KEY_GOTHIC_14),
GColorBlack, GColorClear, GTextAlignmentCenter,
GTextOverflowModeTrailingEllipsis);
layer_add_child(&window->layer, &code_text_layer->layer);
}
prv_adjust_background_frame_for_state(data);
prv_initialize_info_text(data);
}
static void prv_window_unload(Window *window) {
BTPairingUIData *data = window_get_user_data(window);
if (data) {
kino_layer_deinit(&data->kino_layer);
text_layer_deinit(&data->info_text_layer);
text_layer_deinit(&data->device_name_text_layer);
text_layer_deinit(&data->code_text_layer);
gbitmap_destroy(data->approve_bitmap);
gbitmap_destroy(data->decline_bitmap);
action_bar_layer_deinit(&data->action_bar_layer);
new_timer_delete(data->timer);
if (data->ui_state == BTPairingUIStateAwaitingUserConfirmation) {
prv_send_response(false /* is_confirmed */);
}
prv_deinitialize_info_text(data);
property_animation_destroy(data->info_text_in_animation);
property_animation_destroy(data->info_text_out_animation);
kernel_free(data);
}
s_data_ptr = NULL;
}
static void prv_show_failure_kernel_main_cb(void *unused) {
prv_handle_pairing_complete(false /* success */);
}
static void prv_pairing_timeout_timer_callback(void *unused) {
PBL_LOG(LOG_LEVEL_WARNING, "SSP timeout fired!");
launcher_task_add_callback(prv_show_failure_kernel_main_cb, NULL);
}
static void prv_pop_window(void) {
if (s_data_ptr) {
window_stack_remove(&s_data_ptr->window, true /* animated */);
}
}
static void prv_pop_window_kernel_main_cb(void* unused) {
prv_pop_window();
}
static void prv_pop_window_timer_callback(void *unused) {
launcher_task_add_callback(prv_pop_window_kernel_main_cb, NULL);
}
static void prv_push_pairing_window(void) {
BTPairingUIData *data = s_data_ptr;
Window *window = &data->window;
window_init(window, WINDOW_NAME("Bluetooth SSP"));
window_set_window_handlers(window, &(WindowHandlers) {
.load = prv_window_load,
.unload = prv_window_unload,
});
window_set_user_data(window, data);
window_set_overrides_back_button(window, true);
modal_window_push(window, ModalPriorityCritical, true /* animated */);
vibes_double_pulse();
light_enable_interaction();
// This timeout is 0.5s longer than the BT Spec's timeout, to decrease the chances of getting
// a success confirmation right at the max allowed time of 30 secs:
const uint32_t timeout_ms = (30 * 1000) + 500;
data->timer = new_timer_create();
bool success = new_timer_start(data->timer, timeout_ms, prv_pairing_timeout_timer_callback, data,
0 /* flags */);
PBL_ASSERTN(success);
}
static void prv_pop_click_handler(ClickRecognizerRef recognizer, void *ctx) {
prv_pop_window();
}
static void prv_success_or_failure_click_config_provider(void *unused) {
window_single_click_subscribe(BUTTON_ID_BACK, prv_pop_click_handler);
window_single_click_subscribe(BUTTON_ID_UP, prv_pop_click_handler);
window_single_click_subscribe(BUTTON_ID_SELECT, prv_pop_click_handler);
window_single_click_subscribe(BUTTON_ID_DOWN, prv_pop_click_handler);
}
static void prv_create_new_pairing_data(void) {
// If we already have a window up, remove that before we push another
if (s_data_ptr) {
window_stack_remove(&s_data_ptr->window, true /* animated */);
}
BTPairingUIData *data = kernel_malloc_check(sizeof(BTPairingUIData));
*data = (BTPairingUIData){};
data->ui_state = BTPairingUIStateAwaitingUserConfirmation;
s_data_ptr = data;
}
static void prv_handle_confirmation_request(const PairingUserConfirmationCtx *ctx,
PebbleBluetoothPairingConfirmationInfo *info) {
prv_create_new_pairing_data();
s_data_ptr->ctx = ctx;
strncpy(s_data_ptr->device_name_layer_buffer, info->device_name ?: "",
sizeof(s_data_ptr->device_name_layer_buffer));
strncpy(s_data_ptr->code_text_layer_buffer, info->confirmation_token ?: "",
sizeof(s_data_ptr->code_text_layer_buffer));
prv_push_pairing_window();
}
static void prv_handle_pairing_complete(bool success) {
if (!s_data_ptr) {
PBL_LOG(LOG_LEVEL_WARNING, "Dialog was not present, but got complete (%u) event", success);
return;
}
BTPairingUIData *data = s_data_ptr;
if (data->ui_state == BTPairingUIStateAwaitingUserConfirmation) {
prv_exit_awaiting_user_confirmation(data);
} else if (data->ui_state != BTPairingUIStateAwaitingResult) {
PBL_LOG(LOG_LEVEL_WARNING,
"Got completion (%u) but not right state", success);
return;
}
PBL_LOG(LOG_LEVEL_DEBUG, "Got Completion! %u", success);
data->ui_state = success ? BTPairingUIStateSuccess : BTPairingUIStateFailed;
prv_adjust_background_frame_for_state(data);
if (!new_timer_stop(data->timer)) {
// Timer was already executing...
if (success) {
PBL_LOG(LOG_LEVEL_WARNING, "Timeout cb executing while received successful completion event");
}
}
// On failure, leave the message on screen for 60 seconds, on success, only for 5 seconds:
const uint32_t timeout_ms = (success ? 5 : 60) * 1000;
new_timer_start(data->timer, timeout_ms, prv_pop_window_timer_callback, NULL, 0 /* flags */);
window_set_click_config_provider(&data->window, prv_success_or_failure_click_config_provider);
vibes_short_pulse();
light_enable_interaction();
}
void bluetooth_pairing_ui_handle_event(PebbleBluetoothPairEvent *event) {
PBL_ASSERT_TASK(PebbleTask_KernelMain);
switch (event->type) {
case PebbleBluetoothPairEventTypePairingUserConfirmation:
prv_handle_confirmation_request(event->ctx, event->confirmation_info);
break;
case PebbleBluetoothPairEventTypePairingComplete:
if (s_data_ptr && s_data_ptr->ctx == event->ctx) {
prv_handle_pairing_complete(event->success);
} else {
PBL_LOG(LOG_LEVEL_ERROR, "Got complete event for unknown process %p vs %p",
event->ctx, s_data_ptr ? s_data_ptr->ctx : NULL);
}
break;
default:
WTF;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// BT Driver callback implementations:
static void prv_put_pairing_event(const PebbleBluetoothPairEvent *pair_event) {
PebbleEvent event = {
.type = PEBBLE_BT_PAIRING_EVENT,
.bluetooth.pair = *pair_event,
};
event_put(&event);
}
static void prv_copy_string_and_move_cursor(const char *in_str, char **out_str, uint8_t **cursor) {
if (!in_str) {
return;
}
size_t str_size_bytes = strlen(in_str) + 1;
strncpy((char *)*cursor, in_str, str_size_bytes);
*out_str = (char *) *cursor;
*cursor += str_size_bytes;
}
void bt_driver_cb_pairing_confirm_handle_request(const PairingUserConfirmationCtx *ctx,
const char *device_name,
const char *confirmation_token) {
// events.c clean-up (see event_deinit) can only clean up one associated heap allocation,
// so put everything in a single buffer:
size_t device_name_len = device_name ? (strlen(device_name) + 1) : 0;
size_t token_len = confirmation_token ? (strlen(confirmation_token) + 1) : 0;
size_t info_len = (sizeof(PebbleBluetoothPairingConfirmationInfo) + device_name_len + token_len);
uint8_t *cursor = (uint8_t *)kernel_zalloc_check(info_len);
PebbleBluetoothPairingConfirmationInfo *confirmation_info =
(PebbleBluetoothPairingConfirmationInfo *)cursor;
cursor += sizeof(PebbleBluetoothPairingConfirmationInfo);
char *device_name_copy = NULL;
prv_copy_string_and_move_cursor(device_name, &device_name_copy, &cursor);
char *confirmation_token_copy = NULL;
prv_copy_string_and_move_cursor(confirmation_token, &confirmation_token_copy, &cursor);
*confirmation_info = (PebbleBluetoothPairingConfirmationInfo) {
.device_name = device_name_copy,
.confirmation_token = confirmation_token_copy,
};
PebbleBluetoothPairEvent pair_event = {
.type = PebbleBluetoothPairEventTypePairingUserConfirmation,
.ctx = ctx,
.confirmation_info = confirmation_info,
};
prv_put_pairing_event(&pair_event);
}
void bt_driver_cb_pairing_confirm_handle_completed(const PairingUserConfirmationCtx *ctx,
bool success) {
PebbleBluetoothPairEvent pair_event = {
.type = PebbleBluetoothPairEventTypePairingComplete,
.ctx = ctx,
.success = success,
};
prv_put_pairing_event(&pair_event);
}