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 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
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 Advertiser Available: ${bleAdvertiser != null}")
appendLine("GATT Server Active: ${gattServer != null}")
appendLine("Service Registered: $serviceRegistered")
appendLine("Advertising Active: $advertisingActive")
appendLine("Scanning Active: $scanningActive")
appendLine()
appendLine("Connected Devices: ${connectedDevices.size}")
connectedDevices.forEach { (address, device) ->
@@ -210,6 +224,17 @@ class BluetoothConnectionManager(private val context: Context, private val myPee
appendLine(" - ${device.address}")
}
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}")
peripheralRSSI.forEach { (address, rssi) ->
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")
private fun handleScanResult(result: ScanResult) {
val device = result.device
val rssi = result.rssi
val deviceAddress = device.address
// Filter out weak signals
if (rssi < -90) {
return
}
// Check if already connected
if (connectedDevices.values.any { it.address == device.address }) {
// FIXED: Check for existing client connections only, allow server connections
if (gattConnections.containsKey(device)) {
Log.d(TAG, "Already have client connection to $deviceAddress")
return
}
// Store RSSI
peripheralRSSI[device.address] = rssi
// FIXED: Check for pending connections to prevent duplicates
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
connectToDevice(device)
connectToDeviceFixed(device)
}
/**
* Connect to a discovered device
* Connect to a discovered device - FIXED version with proper timeout and cleanup handling
*/
@Suppress("DEPRECATION")
private fun connectToDevice(device: BluetoothDevice) {
private fun connectToDeviceFixed(device: BluetoothDevice) {
if (!hasBluetoothPermissions()) return
val deviceAddress = device.address
Log.d(TAG, "Attempting to connect to $deviceAddress")
val gattCallback = object : BluetoothGattCallback() {
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) {
BluetoothProfile.STATE_CONNECTED -> {
Log.d(TAG, "Connected to ${gatt.device.address} as client")
connectedDevices[gatt.device.address] = gatt.device
gattConnections[gatt.device] = gatt
gatt.discoverServices()
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "Successfully connected to $deviceAddress as client")
connectedDevices[deviceAddress] = gatt.device
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 -> {
Log.d(TAG, "Disconnected from ${gatt.device.address}")
connectedDevices.remove(gatt.device.address)
Log.d(TAG, "Disconnected from $deviceAddress (status: $status)")
connectedDevices.remove(deviceAddress)
deviceCharacteristics.remove(gatt.device)
gattConnections.remove(gatt.device)
gatt.close()
}
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val service = gatt.getService(SERVICE_UUID)
val characteristic = service?.getCharacteristic(CHARACTERISTIC_UUID)
if (characteristic != null) {
deviceCharacteristics[gatt.device] = characteristic
gatt.setCharacteristicNotification(characteristic, true)
pendingConnections.remove(deviceAddress) // Clear pending connection
// Enable notifications
val descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
)
descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
// Handle connection errors
if (status == 147 || status == 133 || status == 8) { // Timeout or connection errors
Log.w(TAG, "Connection failed to $deviceAddress with status $status")
}
// Notify delegate
connectionScope.launch {
delay(200)
delegate?.onDeviceConnected(gatt.device)
// Close the GATT connection
try {
gatt.close()
} catch (e: Exception) {
Log.e(TAG, "Error closing GATT connection to $deviceAddress: ${e.message}")
}
}
}
}
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) {
val value = characteristic.value
Log.d(TAG, "Received ${value.size} bytes from $deviceAddress")
val packet = BitchatPacket.fromBinaryData(value)
if (packet != null) {
val peerID = String(packet.senderID).replace("\u0000", "")
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()
gattConnections.clear()
peripheralRSSI.clear()
// FIXED: Clear new tracking maps
pendingConnections.clear()
connectionAttempts.clear()
}
}