precvent duplicate connections

This commit is contained in:
callebtc
2025-07-09 15:34:20 +02:00
parent d962ed8ccb
commit d3e258b442
@@ -44,6 +44,17 @@ class BluetoothConnectionManager(private val context: Context, private val myPee
private val gattConnections = ConcurrentHashMap<BluetoothDevice, BluetoothGatt>() // Track GATT client connections private val gattConnections = ConcurrentHashMap<BluetoothDevice, BluetoothGatt>() // Track GATT client connections
private val peripheralRSSI = ConcurrentHashMap<String, Int>() // Track RSSI by device address during discovery private val peripheralRSSI = ConcurrentHashMap<String, Int>() // Track RSSI by device address during discovery
// FIXED: Track connection attempts to prevent duplicates
private val pendingConnections = ConcurrentHashMap<String, Long>() // device address -> attempt timestamp
private val connectionAttempts = ConcurrentHashMap<String, Int>() // device address -> attempt count
private val maxConnectionAttempts = 3
private val connectionRetryDelay = 5000L // 5 seconds
// Service registration tracking
private var serviceRegistered = false
private var advertisingActive = false
private var scanningActive = false
// Delegate for callbacks // Delegate for callbacks
var delegate: BluetoothConnectionManagerDelegate? = null var delegate: BluetoothConnectionManagerDelegate? = null
@@ -194,6 +205,9 @@ class BluetoothConnectionManager(private val context: Context, private val myPee
appendLine("BLE Scanner Available: ${bleScanner != null}") appendLine("BLE Scanner Available: ${bleScanner != null}")
appendLine("BLE Advertiser Available: ${bleAdvertiser != null}") appendLine("BLE Advertiser Available: ${bleAdvertiser != null}")
appendLine("GATT Server Active: ${gattServer != null}") appendLine("GATT Server Active: ${gattServer != null}")
appendLine("Service Registered: $serviceRegistered")
appendLine("Advertising Active: $advertisingActive")
appendLine("Scanning Active: $scanningActive")
appendLine() appendLine()
appendLine("Connected Devices: ${connectedDevices.size}") appendLine("Connected Devices: ${connectedDevices.size}")
connectedDevices.forEach { (address, device) -> connectedDevices.forEach { (address, device) ->
@@ -210,6 +224,17 @@ class BluetoothConnectionManager(private val context: Context, private val myPee
appendLine(" - ${device.address}") appendLine(" - ${device.address}")
} }
appendLine() appendLine()
appendLine("Pending Connections: ${pendingConnections.size}")
pendingConnections.forEach { (address, timestamp) ->
val elapsed = (System.currentTimeMillis() - timestamp) / 1000
appendLine(" - $address (${elapsed}s ago)")
}
appendLine()
appendLine("Connection Attempts: ${connectionAttempts.size}")
connectionAttempts.forEach { (address, attempts) ->
appendLine(" - $address: $attempts attempts")
}
appendLine()
appendLine("Peripheral RSSI: ${peripheralRSSI.size}") appendLine("Peripheral RSSI: ${peripheralRSSI.size}")
peripheralRSSI.forEach { (address, rssi) -> peripheralRSSI.forEach { (address, rssi) ->
appendLine(" - $address: $rssi dBm") appendLine(" - $address: $rssi dBm")
@@ -514,94 +539,205 @@ class BluetoothConnectionManager(private val context: Context, private val myPee
} }
/** /**
* Handle scan result and connect to discovered devices * Handle scan result and connect to discovered devices - FIXED to prevent duplicate connections
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun handleScanResult(result: ScanResult) { private fun handleScanResult(result: ScanResult) {
val device = result.device val device = result.device
val rssi = result.rssi val rssi = result.rssi
val deviceAddress = device.address
// Filter out weak signals // Filter out weak signals
if (rssi < -90) { if (rssi < -90) {
return return
} }
// Check if already connected // FIXED: Check for existing client connections only, allow server connections
if (connectedDevices.values.any { it.address == device.address }) { if (gattConnections.containsKey(device)) {
Log.d(TAG, "Already have client connection to $deviceAddress")
return return
} }
// Store RSSI // FIXED: Check for pending connections to prevent duplicates
peripheralRSSI[device.address] = rssi val currentTime = System.currentTimeMillis()
val pendingTime = pendingConnections[deviceAddress]
if (pendingTime != null && (currentTime - pendingTime) < connectionRetryDelay) {
Log.d(TAG, "Connection attempt to $deviceAddress already pending, skipping")
return
}
Log.i(TAG, "Found bitchat service at ${device.address} (RSSI: $rssi), connecting...") // Check connection attempt limits
val attempts = connectionAttempts.getOrDefault(deviceAddress, 0)
if (attempts >= maxConnectionAttempts) {
if (pendingTime == null || (currentTime - pendingTime) > connectionRetryDelay * 2) {
// Reset after extended delay
connectionAttempts[deviceAddress] = 0
pendingConnections.remove(deviceAddress)
Log.d(TAG, "Reset connection attempts for $deviceAddress after extended delay")
} else {
Log.d(TAG, "Max connection attempts reached for $deviceAddress")
return
}
}
// Store RSSI and track connection attempt
peripheralRSSI[deviceAddress] = rssi
pendingConnections[deviceAddress] = currentTime
connectionAttempts[deviceAddress] = attempts + 1
Log.i(TAG, "Found bitchat service at $deviceAddress (RSSI: $rssi), connecting (attempt ${attempts + 1})...")
// Connect to device // Connect to device
connectToDevice(device) connectToDeviceFixed(device)
} }
/** /**
* Connect to a discovered device * Connect to a discovered device - FIXED version with proper timeout and cleanup handling
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
private fun connectToDevice(device: BluetoothDevice) { private fun connectToDeviceFixed(device: BluetoothDevice) {
if (!hasBluetoothPermissions()) return if (!hasBluetoothPermissions()) return
val deviceAddress = device.address
Log.d(TAG, "Attempting to connect to $deviceAddress")
val gattCallback = object : BluetoothGattCallback() { val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val statusMessage = when (status) {
BluetoothGatt.GATT_SUCCESS -> "SUCCESS"
147 -> "CONNECTION_TIMEOUT"
133 -> "GATT_ERROR"
8 -> "CONNECTION_TIMEOUT"
else -> "ERROR_$status"
}
Log.d(TAG, "onConnectionStateChange: $deviceAddress status=$statusMessage($status) newState=$newState")
when (newState) { when (newState) {
BluetoothProfile.STATE_CONNECTED -> { BluetoothProfile.STATE_CONNECTED -> {
Log.d(TAG, "Connected to ${gatt.device.address} as client") if (status == BluetoothGatt.GATT_SUCCESS) {
connectedDevices[gatt.device.address] = gatt.device Log.i(TAG, "Successfully connected to $deviceAddress as client")
gattConnections[gatt.device] = gatt connectedDevices[deviceAddress] = gatt.device
gatt.discoverServices() gattConnections[gatt.device] = gatt
pendingConnections.remove(deviceAddress) // Clear pending connection
// Discover services with a slight delay for stability
connectionScope.launch {
delay(200) // Small delay for connection stabilization
if (gattConnections.containsKey(gatt.device)) {
gatt.discoverServices()
}
}
} else {
Log.e(TAG, "Connected to $deviceAddress with error status: $status")
gatt.disconnect()
}
} }
BluetoothProfile.STATE_DISCONNECTED -> { BluetoothProfile.STATE_DISCONNECTED -> {
Log.d(TAG, "Disconnected from ${gatt.device.address}") Log.d(TAG, "Disconnected from $deviceAddress (status: $status)")
connectedDevices.remove(gatt.device.address) connectedDevices.remove(deviceAddress)
deviceCharacteristics.remove(gatt.device) deviceCharacteristics.remove(gatt.device)
gattConnections.remove(gatt.device) gattConnections.remove(gatt.device)
gatt.close() pendingConnections.remove(deviceAddress) // Clear pending connection
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { // Handle connection errors
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == 147 || status == 133 || status == 8) { // Timeout or connection errors
val service = gatt.getService(SERVICE_UUID) Log.w(TAG, "Connection failed to $deviceAddress with status $status")
val characteristic = service?.getCharacteristic(CHARACTERISTIC_UUID) }
if (characteristic != null) { // Close the GATT connection
deviceCharacteristics[gatt.device] = characteristic try {
gatt.setCharacteristicNotification(characteristic, true) gatt.close()
} catch (e: Exception) {
// Enable notifications Log.e(TAG, "Error closing GATT connection to $deviceAddress: ${e.message}")
val descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
)
descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
// Notify delegate
connectionScope.launch {
delay(200)
delegate?.onDeviceConnected(gatt.device)
} }
} }
} }
} }
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
Log.d(TAG, "onServicesDiscovered: $deviceAddress status=$status")
if (status == BluetoothGatt.GATT_SUCCESS) {
val service = gatt.getService(SERVICE_UUID)
val characteristic = service?.getCharacteristic(CHARACTERISTIC_UUID)
if (characteristic != null) {
Log.d(TAG, "Found bitchat service and characteristic on $deviceAddress")
deviceCharacteristics[gatt.device] = characteristic
// Enable notifications
val notificationSuccess = gatt.setCharacteristicNotification(characteristic, true)
Log.d(TAG, "Set notification for $deviceAddress: $notificationSuccess")
// Write notification descriptor
val descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
)
if (descriptor != null) {
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
val descriptorSuccess = gatt.writeDescriptor(descriptor)
Log.d(TAG, "Write notification descriptor for $deviceAddress: $descriptorSuccess")
}
// Notify delegate about successful connection
connectionScope.launch {
delay(100) // Ensure everything is set up
delegate?.onDeviceConnected(gatt.device)
}
} else {
Log.e(TAG, "Bitchat service or characteristic not found on $deviceAddress")
gatt.disconnect()
}
} else {
Log.e(TAG, "Service discovery failed on $deviceAddress with status: $status")
gatt.disconnect()
}
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
val value = characteristic.value val value = characteristic.value
Log.d(TAG, "Received ${value.size} bytes from $deviceAddress")
val packet = BitchatPacket.fromBinaryData(value) val packet = BitchatPacket.fromBinaryData(value)
if (packet != null) { if (packet != null) {
val peerID = String(packet.senderID).replace("\u0000", "") val peerID = String(packet.senderID).replace("\u0000", "")
delegate?.onPacketReceived(packet, peerID, gatt.device) delegate?.onPacketReceived(packet, peerID, gatt.device)
} else {
Log.w(TAG, "Failed to parse packet from $deviceAddress")
}
}
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
Log.d(TAG, "onDescriptorWrite: $deviceAddress status=$status")
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "Successfully enabled notifications for $deviceAddress")
} else {
Log.e(TAG, "Failed to enable notifications for $deviceAddress: $status")
} }
} }
} }
device.connectGatt(context, false, gattCallback) // Attempt the connection
try {
val gatt = device.connectGatt(context, false, gattCallback)
if (gatt == null) {
Log.e(TAG, "Failed to create GATT connection to $deviceAddress")
pendingConnections.remove(deviceAddress)
}
} catch (e: Exception) {
Log.e(TAG, "Exception connecting to $deviceAddress: ${e.message}")
pendingConnections.remove(deviceAddress)
}
}
/**
* Connect to a discovered device - LEGACY METHOD (kept for compatibility)
*/
@Suppress("DEPRECATION")
private fun connectToDevice(device: BluetoothDevice) {
// Redirect to fixed method
connectToDeviceFixed(device)
} }
/** /**
@@ -613,6 +749,9 @@ class BluetoothConnectionManager(private val context: Context, private val myPee
subscribedDevices.clear() subscribedDevices.clear()
gattConnections.clear() gattConnections.clear()
peripheralRSSI.clear() peripheralRSSI.clear()
// FIXED: Clear new tracking maps
pendingConnections.clear()
connectionAttempts.clear()
} }
} }