Files
bitchat-android/app/src/main/java/com/bitchat/android/mesh/BluetoothConnectionManager.kt
T
2025-07-13 23:23:15 +02:00

235 lines
8.0 KiB
Kotlin

package com.bitchat.android.mesh
import android.bluetooth.*
import android.content.Context
import android.util.Log
import com.bitchat.android.model.RoutedPacket
import com.bitchat.android.protocol.BitchatPacket
import kotlinx.coroutines.*
/**
* Power-optimized Bluetooth connection manager with comprehensive memory management
* Integrates with PowerManager for adaptive power consumption
* Coordinates smaller, focused components for better maintainability
*/
class BluetoothConnectionManager(
private val context: Context,
private val myPeerID: String,
private val fragmentManager: FragmentManager? = null
) : PowerManagerDelegate {
companion object {
private const val TAG = "BluetoothConnectionManager"
}
// Core Bluetooth components
private val bluetoothManager: BluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
// Power management
private val powerManager = PowerManager(context)
// Coroutines
private val connectionScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// Component managers
private val permissionManager = BluetoothPermissionManager(context)
private val connectionTracker = BluetoothConnectionTracker(connectionScope, powerManager)
private val packetBroadcaster = BluetoothPacketBroadcaster(connectionScope, connectionTracker, fragmentManager)
// Delegate for component managers to call back to main manager
private val componentDelegate = object : BluetoothConnectionManagerDelegate {
override fun onPacketReceived(packet: BitchatPacket, peerID: String, device: BluetoothDevice?) {
delegate?.onPacketReceived(packet, peerID, device)
}
override fun onDeviceConnected(device: BluetoothDevice) {
delegate?.onDeviceConnected(device)
}
}
private val serverManager = BluetoothGattServerManager(
context, connectionScope, connectionTracker, permissionManager, powerManager, componentDelegate
)
private val clientManager = BluetoothGattClientManager(
context, connectionScope, connectionTracker, permissionManager, powerManager, componentDelegate
)
// Service state
private var isActive = false
// Delegate for callbacks
var delegate: BluetoothConnectionManagerDelegate? = null
// Public property for address-peer mapping
val addressPeerMap get() = connectionTracker.addressPeerMap
init {
powerManager.delegate = this
}
/**
* Start all Bluetooth services with power optimization
*/
fun startServices(): Boolean {
Log.i(TAG, "Starting power-optimized Bluetooth services...")
if (!permissionManager.hasBluetoothPermissions()) {
Log.e(TAG, "Missing Bluetooth permissions")
return false
}
if (bluetoothAdapter?.isEnabled != true) {
Log.e(TAG, "Bluetooth is not enabled")
return false
}
try {
isActive = true
// Start all component managers
connectionScope.launch {
// Start connection tracker first
connectionTracker.start()
// Start power manager
powerManager.start()
// Start server manager
if (!serverManager.start()) {
Log.e(TAG, "Failed to start server manager")
this@BluetoothConnectionManager.isActive = false
return@launch
}
// Start client manager
if (!clientManager.start()) {
Log.e(TAG, "Failed to start client manager")
this@BluetoothConnectionManager.isActive = false
return@launch
}
Log.i(TAG, "Bluetooth services started successfully")
}
return true
} catch (e: Exception) {
Log.e(TAG, "Failed to start Bluetooth services: ${e.message}")
isActive = false
return false
}
}
/**
* Stop all Bluetooth services with proper cleanup
*/
fun stopServices() {
Log.i(TAG, "Stopping power-optimized Bluetooth services")
isActive = false
connectionScope.launch {
// Stop component managers
clientManager.stop()
serverManager.stop()
// Stop power manager
powerManager.stop()
// Stop connection tracker
connectionTracker.stop()
// Cancel the coroutine scope
connectionScope.cancel()
Log.i(TAG, "All Bluetooth services stopped")
}
}
/**
* Set app background state for power optimization
*/
fun setAppBackgroundState(inBackground: Boolean) {
powerManager.setAppBackgroundState(inBackground)
}
/**
* Broadcast packet to connected devices with connection limit enforcement
* Automatically fragments large packets to fit within BLE MTU limits
*/
fun broadcastPacket(routed: RoutedPacket) {
if (!isActive) return
packetBroadcaster.broadcastPacket(
routed,
serverManager.getGattServer(),
serverManager.getCharacteristic()
)
}
/**
* Get connected device count
*/
fun getConnectedDeviceCount(): Int = connectionTracker.getConnectedDeviceCount()
/**
* Get debug information including power management
*/
fun getDebugInfo(): String {
return buildString {
appendLine("=== Bluetooth Connection Manager ===")
appendLine("Bluetooth MAC Address: ${bluetoothAdapter?.address}")
appendLine("Active: $isActive")
appendLine("Bluetooth Enabled: ${bluetoothAdapter?.isEnabled}")
appendLine("Has Permissions: ${permissionManager.hasBluetoothPermissions()}")
appendLine("GATT Server Active: ${serverManager.getGattServer() != null}")
appendLine()
appendLine(powerManager.getPowerInfo())
appendLine()
appendLine(connectionTracker.getDebugInfo())
}
}
// MARK: - PowerManagerDelegate Implementation
override fun onPowerModeChanged(newMode: PowerManager.PowerMode) {
Log.i(TAG, "Power mode changed to: $newMode")
connectionScope.launch {
// CRITICAL FIX: Avoid rapid scan restarts by checking if we need to change scan behavior
val wasUsingDutyCycle = powerManager.shouldUseDutyCycle()
// Update advertising with new power settings
serverManager.restartAdvertising()
// Only restart scanning if the duty cycle behavior changed
val nowUsingDutyCycle = powerManager.shouldUseDutyCycle()
if (wasUsingDutyCycle != nowUsingDutyCycle) {
Log.d(TAG, "Duty cycle behavior changed (${wasUsingDutyCycle} -> ${nowUsingDutyCycle}), restarting scan")
clientManager.restartScanning()
} else {
Log.d(TAG, "Duty cycle behavior unchanged, keeping existing scan state")
}
// Enforce connection limits
connectionTracker.enforceConnectionLimits()
}
}
override fun onScanStateChanged(shouldScan: Boolean) {
clientManager.onScanStateChanged(shouldScan)
}
// MARK: - Private Implementation - All moved to component managers
}
/**
* Delegate interface for Bluetooth connection manager callbacks
*/
interface BluetoothConnectionManagerDelegate {
fun onPacketReceived(packet: BitchatPacket, peerID: String, device: BluetoothDevice?)
fun onDeviceConnected(device: BluetoothDevice)
}