diff --git a/app/src/main/java/com/bitchat/android/MainActivity.kt b/app/src/main/java/com/bitchat/android/MainActivity.kt index 36fbf4d1..cadc4547 100644 --- a/app/src/main/java/com/bitchat/android/MainActivity.kt +++ b/app/src/main/java/com/bitchat/android/MainActivity.kt @@ -73,6 +73,8 @@ class MainActivity : OrientationAwareActivity() { permissionManager = PermissionManager(this) // Initialize core mesh service first meshService = BluetoothMeshService(this) + // Expose BLE mesh to Wi‑Fi Aware controller for cross-transport relays + try { com.bitchat.android.wifiaware.WifiAwareController.setBleMeshService(meshService) } catch (_: Exception) { } bluetoothStatusManager = BluetoothStatusManager( activity = this, context = this, @@ -347,6 +349,12 @@ class MainActivity : OrientationAwareActivity() { bluetoothStatusManager.logBluetoothStatus() mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus()) + val bleRequired = try { com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true) } catch (_: Exception) { true } + if (!bleRequired) { + // Skip BLE checks entirely when BLE is disabled in debug settings + checkLocationAndProceed() + return + } when (mainViewModel.bluetoothStatus.value) { BluetoothStatus.ENABLED -> { // Bluetooth is enabled, check location services next @@ -513,8 +521,9 @@ class MainActivity : OrientationAwareActivity() { else -> BatteryOptimizationStatus.ENABLED } + val bleRequired2 = try { com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true) } catch (_: Exception) { true } when { - currentBluetoothStatus != BluetoothStatus.ENABLED -> { + bleRequired2 && currentBluetoothStatus != BluetoothStatus.ENABLED -> { // Bluetooth still disabled, but now we have permissions to enable it Log.d("MainActivity", "Permissions granted, but Bluetooth still disabled. Showing Bluetooth enable screen.") mainViewModel.updateBluetoothStatus(currentBluetoothStatus) diff --git a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt index 56c640df..ea41f9c7 100644 --- a/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt +++ b/app/src/main/java/com/bitchat/android/mesh/BluetoothMeshService.kt @@ -249,11 +249,16 @@ class BluetoothMeshService(private val context: Context) { override fun sendPacket(packet: BitchatPacket) { // Sign the packet before broadcasting val signedPacket = signPacketBeforeBroadcast(packet) - connectionManager.broadcastPacket(RoutedPacket(signedPacket)) + val routed = RoutedPacket(signedPacket) + connectionManager.broadcastPacket(routed) + // Cross-transport relay to Wi‑Fi Aware if available + try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.broadcastRoutedPacket(routed) } catch (_: Exception) { } } override fun relayPacket(routed: RoutedPacket) { connectionManager.broadcastPacket(routed) + // Cross-transport relay to Wi‑Fi Aware if available + try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.broadcastRoutedPacket(routed) } catch (_: Exception) { } } override fun getBroadcastRecipient(): ByteArray { diff --git a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt index a3def523..fc134a1b 100644 --- a/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt +++ b/app/src/main/java/com/bitchat/android/ui/MediaSendingManager.kt @@ -210,6 +210,7 @@ class MediaSendingManager( Log.d(TAG, "📤 Calling meshService.sendFilePrivate to $toPeerID") meshService.sendFilePrivate(toPeerID, filePacket) + try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFilePrivate(toPeerID, filePacket) } catch (_: Exception) {} Log.d(TAG, "✅ File send completed successfully") } @@ -264,6 +265,7 @@ class MediaSendingManager( Log.d(TAG, "📤 Calling meshService.sendFileBroadcast") meshService.sendFileBroadcast(filePacket) + try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFileBroadcast(filePacket) } catch (_: Exception) {} Log.d(TAG, "✅ File broadcast completed successfully") } diff --git a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt index 2a6b2035..7c8e5ae6 100644 --- a/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt +++ b/app/src/main/java/com/bitchat/android/ui/SidebarComponents.kt @@ -576,16 +576,21 @@ private fun PeerItem( tint = Color.Gray ) } else { - val wifiAwarePeers by com.bitchat.android.wifiaware.WifiAwareController.connectedPeers.collectAsState() - val isWifiAware = wifiAwarePeers.containsKey(peerID) + val awareConnected by com.bitchat.android.wifiaware.WifiAwareController.connectedPeers.collectAsState() + val awareDiscovered by com.bitchat.android.wifiaware.WifiAwareController.discoveredPeers.collectAsState() + val isWifiDirect = awareConnected.containsKey(peerID) + val isBleDirect = isDirect val icon = when { - isWifiAware -> Icons.Filled.Wifi - isDirect -> Icons.Outlined.SettingsInputAntenna + isWifiDirect -> Icons.Filled.Wifi + isBleDirect -> Icons.Outlined.SettingsInputAntenna + // Routed: show Route icon; optionally prefer Wi‑Fi Aware if discovered there + awareDiscovered.contains(peerID) -> Icons.Filled.WifiTethering else -> Icons.Filled.Route } val cd = when { - isWifiAware -> "Direct Wi‑Fi Aware" - isDirect -> "Direct Bluetooth" + isWifiDirect -> "Direct Wi‑Fi Aware" + isBleDirect -> "Direct Bluetooth" + awareDiscovered.contains(peerID) -> "Routed over Wi‑Fi" else -> "Routed" } Icon( diff --git a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt index baa58a86..d10c6741 100644 --- a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt +++ b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareController.kt @@ -51,7 +51,7 @@ object WifiAwareController { try { val s = service if (s != null) { - _connectedPeers.value = s.getDeviceAddressToPeerMapping().entries.associate { (ip, peer) -> peer to ip } + _connectedPeers.value = s.getDeviceAddressToPeerMapping() // peerID -> ip _knownPeers.value = s.getPeerNicknames() _discoveredPeers.value = s.getDiscoveredPeerIds() } else { @@ -108,4 +108,9 @@ object WifiAwareController { } fun getService(): WifiAwareMeshService? = service + + // Optional bridge to BLE mesh for cross-transport relaying + @Volatile private var bleMesh: com.bitchat.android.mesh.BluetoothMeshService? = null + fun setBleMeshService(svc: com.bitchat.android.mesh.BluetoothMeshService) { bleMesh = svc } + fun getBleMeshService(): com.bitchat.android.mesh.BluetoothMeshService? = bleMesh } diff --git a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshServiceLatest.kt b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshServiceLatest.kt index 87c1d616..c6b12406 100644 --- a/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshServiceLatest.kt +++ b/app/src/main/java/com/bitchat/android/wifi-aware/WifiAwareMeshServiceLatest.kt @@ -172,24 +172,39 @@ class WifiAwareMeshService(private val context: Context) { * Broadcasts routed packet to currently connected peers. */ private fun broadcastPacket(routed: RoutedPacket) { - routed.packet.toBinaryData()?.let { + routed.packet.toBinaryData()?.let { data -> Log.d(TAG, "TX: packet type=${routed.packet.type} broadcast (ttl=${routed.packet.ttl})") - broadcastRaw(it) + serviceScope.launch { broadcastRaw(data) } + // Cross-transport relay (optional): also broadcast via BLE if available + try { com.bitchat.android.wifiaware.WifiAwareController.getBleMeshService()?.connectionManager?.broadcastPacket(routed) } catch (_: Exception) { } } } + // Expose a public method so BLE can forward relays to Wi‑Fi Aware + fun broadcastRoutedPacket(routed: RoutedPacket) { + broadcastPacket(routed) + } + /** * Send packet to connected peer. */ private fun sendPacketToPeer(peerID: String, packet: BitchatPacket) { - val sock = peerSockets[peerID] ?: return - try { - val data = packet.toBinaryData() ?: return - sock.getOutputStream().write(data) - Log.d(TAG, "TX: packet type=${packet.type} → ${peerID.take(8)}… (bytes=${data.size})") - } catch (e: IOException) { - Log.e(TAG, "TX: write to ${peerID.take(8)}… failed: ${e.message}") + val data = packet.toBinaryData() ?: return + serviceScope.launch { + val sock = peerSockets[peerID] + if (sock == null) { + Log.w(TAG, "TX: no socket for ${peerID.take(8)}…") + return@launch + } + try { + sock.getOutputStream().write(data) + Log.d(TAG, "TX: packet type=${packet.type} → ${peerID.take(8)}… (bytes=${data.size})") + } catch (e: IOException) { + Log.e(TAG, "TX: write to ${peerID.take(8)}… failed: ${e.message}") + } } + // Cross-transport relay to BLE when unicast target is connected there too + try { com.bitchat.android.wifiaware.WifiAwareController.getBleMeshService()?.connectionManager?.sendPacketToPeer(peerID, packet) } catch (_: Exception) { } } /** @@ -279,17 +294,19 @@ class WifiAwareMeshService(private val context: Context) { override fun hasNoiseSession(peerID: String) = encryptionService.hasEstablishedSession(peerID) override fun initiateNoiseHandshake(peerID: String) { - val hs = encryptionService.initiateHandshake(peerID) ?: return - val packet = BitchatPacket( - version = 1u, - type = MessageType.NOISE_HANDSHAKE.value, - senderID = hexStringToByteArray(myPeerID), - recipientID = hexStringToByteArray(peerID), - timestamp = System.currentTimeMillis().toULong(), - payload = hs, - ttl = MAX_TTL - ) - broadcastPacket(RoutedPacket(signPacketBeforeBroadcast(packet))) + serviceScope.launch { + val hs = encryptionService.initiateHandshake(peerID) ?: return@launch + val packet = BitchatPacket( + version = 1u, + type = MessageType.NOISE_HANDSHAKE.value, + senderID = hexStringToByteArray(myPeerID), + recipientID = hexStringToByteArray(peerID), + timestamp = System.currentTimeMillis().toULong(), + payload = hs, + ttl = MAX_TTL + ) + broadcastPacket(RoutedPacket(signPacketBeforeBroadcast(packet))) + } } override fun processNoiseHandshakeMessage(payload: ByteArray, peerID: String): ByteArray? = try { encryptionService.processHandshakeMessage(payload, peerID) } catch (_: Exception) { null } @@ -593,8 +610,10 @@ class WifiAwareMeshService(private val context: Context) { // Kick off Noise handshake for this logical peer if (myPeerID < peerId) { messageHandler.delegate?.initiateNoiseHandshake(peerId) - Log.d(TAG, "SERVER: initiating Noise handshake to ${peerId.take(8)}…") + Log.d(TAG, "SERVER: initiating Noise handshake to ${peerId.take(8)}… (lower ID)") } + // Ensure fast presence even before handshake settles + serviceScope.launch { delay(150); sendBroadcastAnnounce() } } catch (ioe: IOException) { Log.e(TAG, "SERVER: accept failed for ${peerId.take(8)}…", ioe) } @@ -707,10 +726,12 @@ class WifiAwareMeshService(private val context: Context) { listenerExec.execute { listenToPeer(sock, peerId) } handleServerKeepAlive(sock, peerId, peerHandle) // Kick off Noise handshake for this logical peer - if (myPeerID > peerId) { + if (myPeerID < peerId) { messageHandler.delegate?.initiateNoiseHandshake(peerId) - Log.d(TAG, "CLIENT: initiating Noise handshake to ${peerId.take(8)}…") + Log.d(TAG, "CLIENT: initiating Noise handshake to ${peerId.take(8)}… (lower ID)") } + // Ensure fast presence even before handshake settles + serviceScope.launch { delay(150); sendBroadcastAnnounce() } } catch (ioe: IOException) { Log.e(TAG, "CLIENT: socket connect failed to ${peerId.take(8)}…", ioe) }