fw/settings: Enforce single phone pairing policy in Bluetooth settings

Fixes FIRM-587

Signed-off-by: Joshua Jun <joshuajun@proton.me>
This commit is contained in:
Joshua Jun 2025-10-29 13:34:15 +01:00 committed by Jinchang
parent 4051c5bb97
commit 71b148b7b7
1 changed files with 60 additions and 22 deletions

View File

@ -92,6 +92,7 @@ typedef struct SettingsBluetoothData {
char header_buffer[HEADER_BUFFER_SIZE]; char header_buffer[HEADER_BUFFER_SIZE];
ToggleState toggle_state; ToggleState toggle_state;
bool did_enable_pairability;
EventServiceInfo bt_airplane_event_info; EventServiceInfo bt_airplane_event_info;
EventServiceInfo bt_connection_event_info; EventServiceInfo bt_connection_event_info;
@ -318,7 +319,27 @@ static void prv_settings_bluetooth_event_handler(PebbleEvent *event, void *conte
case PEBBLE_BLE_HRM_SHARING_STATE_UPDATED_EVENT: case PEBBLE_BLE_HRM_SHARING_STATE_UPDATED_EVENT:
#endif #endif
case PEBBLE_BLE_DEVICE_NAME_UPDATED_EVENT: { case PEBBLE_BLE_DEVICE_NAME_UPDATED_EVENT: {
bool had_remotes = (settings_data->remote_list_head != NULL);
settings_bluetooth_update_remotes_private(settings_data); settings_bluetooth_update_remotes_private(settings_data);
bool has_remotes = (settings_data->remote_list_head != NULL);
// Handle single phone pairing policy: enable/disable advertising based on pairing state
if (had_remotes && !has_remotes) {
// Device was removed, enable advertising
if (!settings_data->did_enable_pairability) {
bt_pairability_use();
settings_data->did_enable_pairability = true;
PBL_LOG(LOG_LEVEL_INFO, "Enabled advertising - no paired devices");
}
} else if (!had_remotes && has_remotes) {
// Device was added, disable advertising
if (settings_data->did_enable_pairability) {
bt_pairability_release();
settings_data->did_enable_pairability = false;
PBL_LOG(LOG_LEVEL_INFO, "Disabled advertising - device paired");
}
}
settings_menu_mark_dirty(SettingsMenuItemBluetooth); settings_menu_mark_dirty(SettingsMenuItemBluetooth);
break; break;
} }
@ -493,12 +514,9 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx,
subtitle = i18n_get("Airplane Mode", data); subtitle = i18n_get("Airplane Mode", data);
icon = &data->icon_heap_bitmap[AirplaneIconIdx]; icon = &data->icon_heap_bitmap[AirplaneIconIdx];
} else { } else {
if (selected) { // Always show device name as subtitle
bt_local_id_copy_device_name(device_name_buffer, false); bt_local_id_copy_device_name(device_name_buffer, false);
subtitle = device_name_buffer; subtitle = device_name_buffer;
} else {
subtitle = i18n_get("Now Discoverable", data);
}
icon = &data->icon_heap_bitmap[BluetoothIconIdx]; icon = &data->icon_heap_bitmap[BluetoothIconIdx];
} }
} else { } else {
@ -512,7 +530,6 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx,
// TODO PBL-23111: Decide how we should show these strings on round displays // TODO PBL-23111: Decide how we should show these strings on round displays
#if PBL_RECT #if PBL_RECT
// Hack: the pairing instruction is drawn in the cell callback, but outside of the cell... // Hack: the pairing instruction is drawn in the cell callback, but outside of the cell...
if (!data->remote_list_head) {
const GDrawState draw_state = ctx->draw_state; const GDrawState draw_state = ctx->draw_state;
// Enable drawing outside of the cell: // Enable drawing outside of the cell:
ctx->draw_state.clip_box = ctx->dest_bitmap.bounds; ctx->draw_state.clip_box = ctx->dest_bitmap.bounds;
@ -525,6 +542,7 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx,
box.size.w -= 30; box.size.w -= 30;
box.size.h = 83; box.size.h = 83;
if (!data->remote_list_head) {
if (bt_ctl_is_airplane_mode_on()) { if (bt_ctl_is_airplane_mode_on()) {
graphics_draw_text(ctx, i18n_get("Disable Airplane Mode to connect.", data), font, graphics_draw_text(ctx, i18n_get("Disable Airplane Mode to connect.", data), font,
box, GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL); box, GTextOverflowModeTrailingEllipsis, GTextAlignmentCenter, NULL);
@ -533,9 +551,17 @@ static void prv_draw_row_cb(SettingsCallbacks *context, GContext *ctx,
font, box, GTextOverflowModeTrailingEllipsis, font, box, GTextOverflowModeTrailingEllipsis,
GTextAlignmentCenter, NULL); GTextAlignmentCenter, NULL);
} }
} else {
// Show message when any phone is paired (even if disconnected)
// Position the message lower to appear below the paired phone row
GRect msg_box = box;
msg_box.origin.y += menu_cell_basic_cell_height() - 10;
graphics_draw_text(ctx, i18n_get("Forget this device to pair a new device.", data),
font, msg_box, GTextOverflowModeTrailingEllipsis,
GTextAlignmentCenter, NULL);
}
ctx->draw_state = draw_state; ctx->draw_state = draw_state;
}
#endif #endif
} else { } else {
const uint16_t device_index = row - 1; const uint16_t device_index = row - 1;
@ -606,7 +632,14 @@ static void prv_expand_cb(SettingsCallbacks *context) {
event_service_client_subscribe(&data->bt_connection_event_info); event_service_client_subscribe(&data->bt_connection_event_info);
event_service_client_subscribe(&data->bt_pairing_event_info); event_service_client_subscribe(&data->bt_pairing_event_info);
event_service_client_subscribe(&data->ble_device_name_updated_event_info); event_service_client_subscribe(&data->ble_device_name_updated_event_info);
// Only enable pairing/advertising if there are no paired devices (single phone policy)
data->did_enable_pairability = false;
if (!data->remote_list_head) {
bt_pairability_use(); bt_pairability_use();
data->did_enable_pairability = true;
}
bt_driver_reconnect_pause(); bt_driver_reconnect_pause();
// Reload & redraw after pairing popup // Reload & redraw after pairing popup
app_focus_service_subscribe_handlers((AppFocusHandlers) { .did_focus = prv_focus_handler }); app_focus_service_subscribe_handlers((AppFocusHandlers) { .did_focus = prv_focus_handler });
@ -617,7 +650,12 @@ static void prv_expand_cb(SettingsCallbacks *context) {
// they consume a fair amount of power // they consume a fair amount of power
static void prv_hide_cb(SettingsCallbacks *context) { static void prv_hide_cb(SettingsCallbacks *context) {
SettingsBluetoothData *data = (SettingsBluetoothData *) context; SettingsBluetoothData *data = (SettingsBluetoothData *) context;
// Only release pairability if we enabled it
if (data->did_enable_pairability) {
bt_pairability_release(); bt_pairability_release();
}
bt_driver_reconnect_resume(); bt_driver_reconnect_resume();
bt_driver_reconnect_reset_interval(); bt_driver_reconnect_reset_interval();
bt_driver_reconnect_try_now(false /*ignore_paused*/); bt_driver_reconnect_try_now(false /*ignore_paused*/);