diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java index 83c710b50a..b92205e213 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHardware.java @@ -29,6 +29,7 @@ import java.util.HashMap; import java.util.Map; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; /** * Unified source of truth for Pebble hardware definitions. @@ -469,6 +470,20 @@ public class PebbleHardware { return name != null && (name.startsWith("Pebble-LE ") || name.startsWith("Pebble Time LE ")); } + /** + * Check if a device candidate needs connect-first pairing (GATT connection before createBond). + * Uses manufacturer data for reliable identification, falling back to name-based detection. + */ + public static boolean needsConnectFirstPairing(GBDeviceCandidate candidate) { + if (candidate == null) { + return false; + } + if (isBleOnly(candidate.getManufacturerSpecificData())) { + return true; + } + return isBleOnly(candidate.getDevice()); + } + // ======================= Platform Utilities ======================= /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java index 95010c446b..e124f9f809 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebblePairingActivity.java @@ -130,8 +130,8 @@ public class PebblePairingActivity extends AbstractGBActivity implements Bonding // Note: These devices are BLE-only, so we don't need the "pebble_force_le" setting - // PebbleIoThread automatically uses BLE based on isPebble2() detection. // Use deviceCandidate to leverage manufacturer data for reliable device identification. - if (BondingUtil.needsConnectFirstPairing(deviceCandidate)) { - LOG.info("Pebble 2/Time 2/2 Duo detected via manufacturer data - using connect-first pairing (BLE-only device)"); + if (PebbleHardware.needsConnectFirstPairing(deviceCandidate)) { + LOG.info("BLE-only device detected - using connect-first pairing"); registerBroadcastReceivers(); @@ -198,7 +198,8 @@ public class PebblePairingActivity extends AbstractGBActivity implements Bonding LOG.debug("ONBONDINGCOMPLETE"); unregisterBroadcastReceivers(); - // Trigger device list refresh so the new device appears immediately + BluetoothDevice btDevice = deviceCandidate != null ? deviceCandidate.getDevice() : null; + if (success) { LocalBroadcastManager.getInstance(this).sendBroadcast( new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST)); @@ -210,9 +211,8 @@ public class PebblePairingActivity extends AbstractGBActivity implements Bonding startActivity(new Intent(this, DiscoveryActivityV2.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)); } - // If it's not a LE Pebble, initiate a connection when bonding is complete - if (!PebbleHardware.isLePebbleCompanion(getCurrentTarget().getDevice()) && success) { - BondingUtil.attemptToFirstConnect(getCurrentTarget().getDevice()); + if (btDevice != null && success && getAttemptToConnect() && shouldReconnectAfterBond()) { + BondingUtil.attemptToFirstConnect(btDevice); } finish(); } @@ -224,7 +224,14 @@ public class PebblePairingActivity extends AbstractGBActivity implements Bonding @Override public boolean getAttemptToConnect() { - return true; + // LE companion manages its own connection via connectThenComplete + return deviceCandidate == null || !PebbleHardware.isLePebbleCompanion(deviceCandidate.getDevice()); + } + + @Override + public boolean shouldReconnectAfterBond() { + // Connect-first pairing: bonding occurred within the existing GATT connection + return !PebbleHardware.needsConnectFirstPairing(deviceCandidate); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java index 8da3f9392e..05b7230cfe 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingInterface.java @@ -54,6 +54,15 @@ public interface BondingInterface { } boolean getAttemptToConnect(); + + /** + * Whether the connection should be restarted after bonding completes. + * Return false for connect-first pairing flows where bonding occurs within an existing + * GATT connection — the connection should not be interrupted. + */ + default boolean shouldReconnectAfterBond() { + return true; + } /** * This forces bonding activities to handle the addition * of all broadcast receivers in the same place diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java index c9c502056f..92f59860c1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/BondingUtil.java @@ -134,14 +134,18 @@ public class BondingUtil { } else { switch (bondState) { case BluetoothDevice.BOND_BONDED: { + LOG.info("Bonded with {}", device.getAddress()); - if (PebbleHardware.isLePebbleCompanion(device) || PebbleHardware.isBleOnly(device) || !bondingInterface.getAttemptToConnect()) { - // Do not initiate connection to LE Pebble and Pebble 2/Time 2/2 Duo - // For Pebble 2/Time 2/2 Duo, the connection was already started before bonding - // Just signal that bonding is complete - LOG.info("LE/Pebble2/Pebble2Duo device bonded - signaling completion"); + if (!bondingInterface.getAttemptToConnect()) { + LOG.info("Device bonded - notifying onBondingComplete without reconnecting."); bondingInterface.onBondingComplete(true); + } else if (!bondingInterface.shouldReconnectAfterBond()) { + // connect-first pairing, existing connection completes to INITIALIZED + // Don't interrupt by disconnecting and reconnecting in the middle of the pairing flow. + LOG.info("Device bonded - connect first pairing, connection already established."); } else { + // Bond-then-connect flow: reconnect now that bonding is complete. + LOG.info("Device bonded - reconnecting and waiting for initialization"); attemptToFirstConnect(device); } return; @@ -334,34 +338,6 @@ public class BondingUtil { } } - /** - * Checks if device needs connect-first pairing (GATT connection before createBond). - * Pebble 2, Pebble Time 2, and Pebble 2 Duo require writing to a pairing trigger - * characteristic before Android's createBond() will succeed. - * - * @param candidate Device candidate with manufacturer data from BLE scan - * @return true if this device needs connect-first pairing - */ - public static boolean needsConnectFirstPairing(GBDeviceCandidate candidate) { - if (candidate == null) { - return false; - } - // First try manufacturer data - this is the most reliable method - if (PebbleHardware.isBleOnly(candidate.getManufacturerSpecificData())) { - return true; - } - // Fall back to name-based detection - return PebbleHardware.isBleOnly(candidate.getDevice()); - } - - /** - * Checks if device needs connect-first pairing (GATT connection before createBond). - * Uses name-based detection when GBDeviceCandidate is not available. - */ - public static boolean needsConnectFirstPairing(BluetoothDevice device) { - return PebbleHardware.isBleOnly(device); - } - /** * Uses the CompanionDeviceManager bonding method */