Revert "fix: wifi aware socket binding (#533)" (#535)

This reverts commit 0c7505b588.
This commit is contained in:
callebtc
2026-01-03 23:37:49 +07:00
committed by GitHub
parent 0c7505b588
commit fa1978d587
18 changed files with 17 additions and 1722 deletions
-9
View File
@@ -5,7 +5,6 @@
<!-- Internet permissions for Nostr relay connections --> <!-- Internet permissions for Nostr relay connections -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<!-- Bluetooth permissions --> <!-- Bluetooth permissions -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
@@ -20,12 +19,6 @@
<!-- Notification permissions --> <!-- Notification permissions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- WiFi / WiFi Aware permissions -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- Android 13+ runtime permission for WiFi operations (including Aware) -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<!-- Signature permission for internal UI shutdown broadcasts --> <!-- Signature permission for internal UI shutdown broadcasts -->
<uses-permission android:name="com.bitchat.android.permission.FORCE_FINISH" /> <uses-permission android:name="com.bitchat.android.permission.FORCE_FINISH" />
<!-- Foreground service and boot permissions for long-running background mesh --> <!-- Foreground service and boot permissions for long-running background mesh -->
@@ -54,8 +47,6 @@
<!-- Hardware features --> <!-- Hardware features -->
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth" android:required="true" /> <uses-feature android:name="android.hardware.bluetooth" android:required="true" />
<!-- Device support hint for WiFi Aware (optional) -->
<uses-feature android:name="android.hardware.wifi.aware" android:required="false" />
<permission <permission
android:name="com.bitchat.android.permission.FORCE_FINISH" android:name="com.bitchat.android.permission.FORCE_FINISH"
@@ -41,11 +41,6 @@ class BitchatApplication : Application() {
// Initialize debug preference manager (persists debug toggles) // Initialize debug preference manager (persists debug toggles)
try { com.bitchat.android.ui.debug.DebugPreferenceManager.init(this) } catch (_: Exception) { } try { com.bitchat.android.ui.debug.DebugPreferenceManager.init(this) } catch (_: Exception) { }
// Initialize WiFi Aware controller with persisted default
try {
val enabled = com.bitchat.android.ui.debug.DebugPreferenceManager.getWifiAwareEnabled(false)
com.bitchat.android.wifiaware.WifiAwareController.initialize(this, enabled)
} catch (_: Exception) { }
// Initialize mesh service preferences // Initialize mesh service preferences
try { com.bitchat.android.service.MeshServicePreferences.init(this) } catch (_: Exception) { } try { com.bitchat.android.service.MeshServicePreferences.init(this) } catch (_: Exception) { }
@@ -112,8 +112,6 @@ class MainActivity : OrientationAwareActivity() {
// Ensure foreground service is running and get mesh instance from holder // Ensure foreground service is running and get mesh instance from holder
try { com.bitchat.android.service.MeshForegroundService.start(applicationContext) } catch (_: Exception) { } try { com.bitchat.android.service.MeshForegroundService.start(applicationContext) } catch (_: Exception) { }
meshService = com.bitchat.android.service.MeshServiceHolder.getOrCreate(applicationContext) meshService = com.bitchat.android.service.MeshServiceHolder.getOrCreate(applicationContext)
// Expose BLE mesh to WiFi Aware controller for cross-transport relays
try { com.bitchat.android.wifiaware.WifiAwareController.setBleMeshService(meshService) } catch (_: Exception) { }
bluetoothStatusManager = BluetoothStatusManager( bluetoothStatusManager = BluetoothStatusManager(
activity = this, activity = this,
context = this, context = this,
@@ -161,52 +159,6 @@ class MainActivity : OrientationAwareActivity() {
} }
} }
} }
// Bridge WiFi Aware callbacks into ChatViewModel (reusing BLE delegate methods)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
com.bitchat.android.wifiaware.WifiAwareController.running.collect { running ->
val svc = com.bitchat.android.wifiaware.WifiAwareController.getService()
if (running && svc != null) {
svc.delegate = object : com.bitchat.android.wifiaware.WifiAwareMeshDelegate {
override fun didReceiveMessage(message: com.bitchat.android.model.BitchatMessage) {
if (message.isPrivate) {
message.senderPeerID?.let { pid -> com.bitchat.android.services.AppStateStore.addPrivateMessage(pid, message) }
} else if (message.channel != null) {
com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel, message)
} else {
com.bitchat.android.services.AppStateStore.addPublicMessage(message)
}
chatViewModel.didReceiveMessage(message)
}
override fun didUpdatePeerList(peers: List<String>) {
chatViewModel.didUpdatePeerList(peers)
}
override fun didReceiveChannelLeave(channel: String, fromPeer: String) {
chatViewModel.didReceiveChannelLeave(channel, fromPeer)
}
override fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String) {
chatViewModel.didReceiveDeliveryAck(messageID, recipientPeerID)
}
override fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) {
chatViewModel.didReceiveReadReceipt(messageID, recipientPeerID)
}
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
return chatViewModel.decryptChannelMessage(encryptedContent, channel)
}
override fun getNickname(): String? {
return chatViewModel.getNickname()
}
override fun isFavorite(peerID: String): Boolean {
return try {
com.bitchat.android.favorites.FavoritesPersistenceService.shared.getFavoriteStatus(peerID)?.isMutual == true
} catch (_: Exception) { false }
}
}
}
}
}
}
// Only start onboarding process if we're in the initial CHECKING state // Only start onboarding process if we're in the initial CHECKING state
// This prevents restarting onboarding on configuration changes // This prevents restarting onboarding on configuration changes
@@ -395,12 +347,6 @@ class MainActivity : OrientationAwareActivity() {
bluetoothStatusManager.logBluetoothStatus() bluetoothStatusManager.logBluetoothStatus()
mainViewModel.updateBluetoothStatus(bluetoothStatusManager.checkBluetoothStatus()) 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) { when (mainViewModel.bluetoothStatus.value) {
BluetoothStatus.ENABLED -> { BluetoothStatus.ENABLED -> {
// Bluetooth is enabled, check location services next // Bluetooth is enabled, check location services next
@@ -567,9 +513,8 @@ class MainActivity : OrientationAwareActivity() {
else -> BatteryOptimizationStatus.ENABLED else -> BatteryOptimizationStatus.ENABLED
} }
val bleRequired2 = try { com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true) } catch (_: Exception) { true }
when { when {
bleRequired2 && currentBluetoothStatus != BluetoothStatus.ENABLED -> { currentBluetoothStatus != BluetoothStatus.ENABLED -> {
// Bluetooth still disabled, but now we have permissions to enable it // Bluetooth still disabled, but now we have permissions to enable it
Log.d("MainActivity", "Permissions granted, but Bluetooth still disabled. Showing Bluetooth enable screen.") Log.d("MainActivity", "Permissions granted, but Bluetooth still disabled. Showing Bluetooth enable screen.")
mainViewModel.updateBluetoothStatus(currentBluetoothStatus) mainViewModel.updateBluetoothStatus(currentBluetoothStatus)
@@ -94,9 +94,6 @@ class BluetoothMeshService(private val context: Context) {
} catch (_: Exception) { 0.01 } } catch (_: Exception) { 0.01 }
} }
) )
// Register as shared instance for Wi-Fi Aware transport
com.bitchat.android.service.MeshServiceHolder.setGossipManager(gossipSyncManager)
// Wire sync manager delegate // Wire sync manager delegate
gossipSyncManager.delegate = object : GossipSyncManager.Delegate { gossipSyncManager.delegate = object : GossipSyncManager.Delegate {
@@ -271,8 +268,7 @@ class BluetoothMeshService(private val context: Context) {
override fun sendPacket(packet: BitchatPacket) { override fun sendPacket(packet: BitchatPacket) {
// Sign the packet before broadcasting // Sign the packet before broadcasting
val signedPacket = signPacketBeforeBroadcast(packet) val signedPacket = signPacketBeforeBroadcast(packet)
val routed = RoutedPacket(signedPacket) connectionManager.broadcastPacket(RoutedPacket(signedPacket))
connectionManager.broadcastPacket(routed)
} }
override fun relayPacket(routed: RoutedPacket) { override fun relayPacket(routed: RoutedPacket) {
@@ -209,7 +209,6 @@ class OnboardingCoordinator(
return when { return when {
permission.contains("BLUETOOTH") -> "Bluetooth/Nearby Devices" permission.contains("BLUETOOTH") -> "Bluetooth/Nearby Devices"
permission.contains("LOCATION") -> "Location (for Bluetooth scanning)" permission.contains("LOCATION") -> "Location (for Bluetooth scanning)"
permission.contains("NEARBY_WIFI") -> "Nearby WiFi Devices (for WiFi Aware)"
permission.contains("NOTIFICATION") -> "Notifications" permission.contains("NOTIFICATION") -> "Notifications"
else -> permission.substringAfterLast(".") else -> permission.substringAfterLast(".")
} }
@@ -11,7 +11,6 @@ import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.Power import androidx.compose.material.icons.filled.Power
import androidx.compose.material.icons.filled.Mic import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Security import androidx.compose.material.icons.filled.Security
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -243,7 +242,6 @@ private fun getPermissionIcon(permissionType: PermissionType): ImageVector {
PermissionType.PRECISE_LOCATION -> Icons.Filled.LocationOn PermissionType.PRECISE_LOCATION -> Icons.Filled.LocationOn
PermissionType.MICROPHONE -> Icons.Filled.Mic PermissionType.MICROPHONE -> Icons.Filled.Mic
PermissionType.NOTIFICATIONS -> Icons.Filled.Notifications PermissionType.NOTIFICATIONS -> Icons.Filled.Notifications
PermissionType.WIFI_AWARE -> Icons.Filled.Wifi
PermissionType.BATTERY_OPTIMIZATION -> Icons.Filled.Power PermissionType.BATTERY_OPTIMIZATION -> Icons.Filled.Power
PermissionType.OTHER -> Icons.Filled.Settings PermissionType.OTHER -> Icons.Filled.Settings
} }
@@ -67,11 +67,6 @@ class PermissionManager(private val context: Context) {
Manifest.permission.ACCESS_FINE_LOCATION Manifest.permission.ACCESS_FINE_LOCATION
)) ))
// WiFi Aware: Android 13+ requires NEARBY_WIFI_DEVICES runtime permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions.add(Manifest.permission.NEARBY_WIFI_DEVICES)
}
// Notification permission intentionally excluded to keep it optional // Notification permission intentionally excluded to keep it optional
return permissions return permissions
@@ -182,20 +177,6 @@ class PermissionManager(private val context: Context) {
) )
) )
// WiFi Aware category (Android 13+)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val wifiAwarePermissions = listOf(Manifest.permission.NEARBY_WIFI_DEVICES)
categories.add(
PermissionCategory(
type = PermissionType.WIFI_AWARE,
description = "Enable WiFi Aware to discover and connect to nearby bitchat users over WiFi.",
permissions = wifiAwarePermissions,
isGranted = wifiAwarePermissions.all { isPermissionGranted(it) },
systemDescription = "Allow bitchat to discover nearby WiFi devices"
)
)
}
// Notifications category (if applicable) // Notifications category (if applicable)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
categories.add( categories.add(
@@ -281,7 +262,6 @@ enum class PermissionType(val nameValue: String) {
PRECISE_LOCATION("Precise Location"), PRECISE_LOCATION("Precise Location"),
MICROPHONE("Microphone"), MICROPHONE("Microphone"),
NOTIFICATIONS("Notifications"), NOTIFICATIONS("Notifications"),
WIFI_AWARE("WiFi Aware"),
BATTERY_OPTIMIZATION("Battery Optimization"), BATTERY_OPTIMIZATION("Battery Optimization"),
OTHER("Other") OTHER("Other")
} }
@@ -9,12 +9,6 @@ import com.bitchat.android.mesh.BluetoothMeshService
*/ */
object MeshServiceHolder { object MeshServiceHolder {
private const val TAG = "MeshServiceHolder" private const val TAG = "MeshServiceHolder"
@Volatile
var sharedGossipSyncManager: com.bitchat.android.sync.GossipSyncManager? = null
private set
fun setGossipManager(mgr: com.bitchat.android.sync.GossipSyncManager) { sharedGossipSyncManager = mgr }
@Volatile @Volatile
var meshService: BluetoothMeshService? = null var meshService: BluetoothMeshService? = null
private set private set
@@ -72,15 +72,9 @@ class MessageRouter private constructor(
val hasMesh = mesh.getPeerInfo(toPeerID)?.isConnected == true val hasMesh = mesh.getPeerInfo(toPeerID)?.isConnected == true
val hasEstablished = mesh.hasEstablishedSession(toPeerID) val hasEstablished = mesh.hasEstablishedSession(toPeerID)
// Check WiFi Aware availability as a secondary transport
val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null }
val hasAware = try { aware?.getPeerInfo(toPeerID)?.isConnected == true && aware.hasEstablishedSession(toPeerID) } catch (_: Exception) { false }
if (hasMesh && hasEstablished) { if (hasMesh && hasEstablished) {
Log.d(TAG, "Routing PM via mesh to ${toPeerID} msg_id=${messageID.take(8)}") Log.d(TAG, "Routing PM via mesh to ${toPeerID} msg_id=${messageID.take(8)}")
mesh.sendPrivateMessage(content, toPeerID, recipientNickname, messageID) mesh.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
} else if (hasAware) {
Log.d(TAG, "Routing PM via WiFi Aware to ${toPeerID} msg_id=${messageID.take(8)}")
aware?.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
} else if (canSendViaNostr(toPeerID)) { } else if (canSendViaNostr(toPeerID)) {
Log.d(TAG, "Routing PM via Nostr to ${toPeerID.take(32)}… msg_id=${messageID.take(8)}") Log.d(TAG, "Routing PM via Nostr to ${toPeerID.take(32)}… msg_id=${messageID.take(8)}")
nostr.sendPrivateMessage(content, toPeerID, recipientNickname, messageID) nostr.sendPrivateMessage(content, toPeerID, recipientNickname, messageID)
@@ -89,21 +83,14 @@ class MessageRouter private constructor(
val q = outbox.getOrPut(toPeerID) { mutableListOf() } val q = outbox.getOrPut(toPeerID) { mutableListOf() }
q.add(Triple(content, recipientNickname, messageID)) q.add(Triple(content, recipientNickname, messageID))
Log.d(TAG, "Initiating noise handshake after queueing PM for ${toPeerID.take(8)}") Log.d(TAG, "Initiating noise handshake after queueing PM for ${toPeerID.take(8)}")
if (hasMesh) mesh.initiateNoiseHandshake(toPeerID) else aware?.initiateNoiseHandshake(toPeerID) mesh.initiateNoiseHandshake(toPeerID)
} }
} }
fun sendReadReceipt(receipt: ReadReceipt, toPeerID: String) { fun sendReadReceipt(receipt: ReadReceipt, toPeerID: String) {
val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService() } catch (_: Exception) { null } if ((mesh.getPeerInfo(toPeerID)?.isConnected == true) && mesh.hasEstablishedSession(toPeerID)) {
val viaMesh = (mesh.getPeerInfo(toPeerID)?.isConnected == true) && mesh.hasEstablishedSession(toPeerID)
val viaAware = try { aware?.getPeerInfo(toPeerID)?.isConnected == true && aware.hasEstablishedSession(toPeerID) } catch (_: Exception) { false }
if (viaMesh) {
Log.d(TAG, "Routing READ via mesh to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}") Log.d(TAG, "Routing READ via mesh to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}")
mesh.sendReadReceipt(receipt.originalMessageID, toPeerID, mesh.getPeerNicknames()[toPeerID] ?: mesh.myPeerID) mesh.sendReadReceipt(receipt.originalMessageID, toPeerID, mesh.getPeerNicknames()[toPeerID] ?: mesh.myPeerID)
} else if (viaAware) {
Log.d(TAG, "Routing READ via WiFi Aware to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}")
val me = try { aware?.myPeerID } catch (_: Exception) { null }
aware?.sendReadReceipt(receipt.originalMessageID, toPeerID, me ?: "")
} else { } else {
Log.d(TAG, "Routing READ via Nostr to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}") Log.d(TAG, "Routing READ via Nostr to ${toPeerID.take(8)}… id=${receipt.originalMessageID.take(8)}")
nostr.sendReadReceipt(receipt, toPeerID) nostr.sendReadReceipt(receipt, toPeerID)
@@ -441,9 +441,8 @@ class ChatViewModel(
state.getNicknameValue() state.getNicknameValue()
) )
} else { } else {
// Default: route via mesh + WiFi Aware // Default: route via mesh
meshService.sendMessage(messageContent, mentions, channel) meshService.sendMessage(messageContent, mentions, channel)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(messageContent, mentions, channel) } catch (_: Exception) {}
} }
}) })
return return
@@ -513,23 +512,19 @@ class ChatViewModel(
state.getNicknameValue(), state.getNicknameValue(),
meshService.myPeerID, meshService.myPeerID,
onEncryptedPayload = { encryptedData -> onEncryptedPayload = { encryptedData ->
// Send encrypted payload announcement over both transports for reachability // This would need proper mesh service integration
meshService.sendMessage(content, mentions, currentChannelValue) meshService.sendMessage(content, mentions, currentChannelValue)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
}, },
onFallback = { onFallback = {
meshService.sendMessage(content, mentions, currentChannelValue) meshService.sendMessage(content, mentions, currentChannelValue)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
} }
) )
} else { } else {
meshService.sendMessage(content, mentions, currentChannelValue) meshService.sendMessage(content, mentions, currentChannelValue)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, currentChannelValue) } catch (_: Exception) {}
} }
} else { } else {
messageManager.addMessage(message) messageManager.addMessage(message)
meshService.sendMessage(content, mentions, null) meshService.sendMessage(content, mentions, null)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendMessage(content, mentions, null) } catch (_: Exception) {}
} }
} }
} }
@@ -652,23 +647,16 @@ class ChatViewModel(
val fingerprints = privateChatManager.getAllPeerFingerprints() val fingerprints = privateChatManager.getAllPeerFingerprints()
state.setPeerFingerprints(fingerprints) state.setPeerFingerprints(fingerprints)
// Merge nicknames from BLE and WiFi Aware to display names for all peers val nicknames = meshService.getPeerNicknames()
val bleNick = meshService.getPeerNicknames() state.setPeerNicknames(nicknames)
val awareNickRaw = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerNicknamesMap() } catch (_: Exception) { null }
val mergedNick = if (awareNickRaw != null) bleNick + awareNickRaw.filter { it.value != null }.mapValues { it.value!! }.filterKeys { it !in bleNick || bleNick[it].isNullOrBlank() } else bleNick
state.setPeerNicknames(mergedNick)
val rssiValues = meshService.getPeerRSSI() val rssiValues = meshService.getPeerRSSI()
val awareRssi = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerRSSI() } catch (_: Exception) { null } state.setPeerRSSI(rssiValues)
val mergedRssi = if (awareRssi != null) rssiValues + awareRssi.filterKeys { it !in rssiValues } else rssiValues
state.setPeerRSSI(mergedRssi)
// Update directness per peer (driven by PeerManager state) // Update directness per peer (driven by PeerManager state)
try { try {
val directMap = state.getConnectedPeersValue().associateWith { pid -> val directMap = state.getConnectedPeersValue().associateWith { pid ->
val ble = meshService.getPeerInfo(pid)?.isDirectConnection == true meshService.getPeerInfo(pid)?.isDirectConnection == true
val aware = try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.getPeerInfo(pid)?.isDirectConnection == true } catch (_: Exception) { false }
ble || aware
} }
state.setPeerDirect(directMap) state.setPeerDirect(directMap)
} catch (_: Exception) { } } catch (_: Exception) { }
@@ -210,7 +210,6 @@ class MediaSendingManager(
Log.d(TAG, "📤 Calling meshService.sendFilePrivate to $toPeerID") Log.d(TAG, "📤 Calling meshService.sendFilePrivate to $toPeerID")
meshService.sendFilePrivate(toPeerID, filePacket) meshService.sendFilePrivate(toPeerID, filePacket)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFilePrivate(toPeerID, filePacket) } catch (_: Exception) {}
Log.d(TAG, "✅ File send completed successfully") Log.d(TAG, "✅ File send completed successfully")
} }
@@ -265,7 +264,6 @@ class MediaSendingManager(
Log.d(TAG, "📤 Calling meshService.sendFileBroadcast") Log.d(TAG, "📤 Calling meshService.sendFileBroadcast")
meshService.sendFileBroadcast(filePacket) meshService.sendFileBroadcast(filePacket)
try { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendFileBroadcast(filePacket) } catch (_: Exception) {}
Log.d(TAG, "✅ File broadcast completed successfully") Log.d(TAG, "✅ File broadcast completed successfully")
} }
@@ -96,10 +96,7 @@ class MeshDelegateHandler(
override fun didUpdatePeerList(peers: List<String>) { override fun didUpdatePeerList(peers: List<String>) {
coroutineScope.launch { coroutineScope.launch {
// Merge peers from multiple transports to avoid flapping state.setConnectedPeers(peers)
val current = state.getConnectedPeersValue().toMutableSet()
current.addAll(peers)
state.setConnectedPeers(current.toList())
state.setIsConnected(peers.isNotEmpty()) state.setIsConnected(peers.isNotEmpty())
notificationManager.showActiveUserNotification(peers) notificationManager.showActiveUserNotification(peers)
// Flush router outbox for any peers that just connected (and their noiseHex aliases) // Flush router outbox for any peers that just connected (and their noiseHex aliases)
@@ -576,26 +576,9 @@ private fun PeerItem(
tint = Color.Gray tint = Color.Gray
) )
} else { } else {
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 {
isWifiDirect -> Icons.Filled.Wifi
isBleDirect -> Icons.Outlined.SettingsInputAntenna
// Routed: show Route icon; optionally prefer WiFi Aware if discovered there
awareDiscovered.contains(peerID) -> Icons.Filled.WifiTethering
else -> Icons.Filled.Route
}
val cd = when {
isWifiDirect -> "Direct WiFi Aware"
isBleDirect -> "Direct Bluetooth"
awareDiscovered.contains(peerID) -> "Routed over WiFi"
else -> "Routed"
}
Icon( Icon(
imageVector = icon, imageVector = if (isDirect) Icons.Outlined.SettingsInputAntenna else Icons.Filled.Route,
contentDescription = cd, contentDescription = if (isDirect) "Direct Bluetooth" else "Routed",
modifier = Modifier.size(16.dp), modifier = Modifier.size(16.dp),
tint = colorScheme.onSurface.copy(alpha = 0.8f) tint = colorScheme.onSurface.copy(alpha = 0.8f)
) )
@@ -20,10 +20,7 @@ object DebugPreferenceManager {
// GCS keys (no migration/back-compat) // GCS keys (no migration/back-compat)
private const val KEY_GCS_MAX_BYTES = "gcs_max_filter_bytes" private const val KEY_GCS_MAX_BYTES = "gcs_max_filter_bytes"
private const val KEY_GCS_FPR = "gcs_filter_fpr_percent" private const val KEY_GCS_FPR = "gcs_filter_fpr_percent"
// Transport master toggles // Removed: persistent notification toggle is now governed by MeshServicePreferences.isBackgroundEnabled
private const val KEY_BLE_ENABLED = "ble_enabled"
private const val KEY_WIFI_AWARE_ENABLED = "wifi_aware_enabled"
private const val KEY_WIFI_AWARE_VERBOSE = "wifi_aware_verbose"
private lateinit var prefs: SharedPreferences private lateinit var prefs: SharedPreferences
@@ -105,25 +102,5 @@ object DebugPreferenceManager {
if (ready()) prefs.edit().putLong(KEY_GCS_FPR, java.lang.Double.doubleToRawLongBits(value)).apply() if (ready()) prefs.edit().putLong(KEY_GCS_FPR, java.lang.Double.doubleToRawLongBits(value)).apply()
} }
// Transport toggles // No longer storing persistent notification in debug prefs.
fun getBleEnabled(default: Boolean = true): Boolean =
if (ready()) prefs.getBoolean(KEY_BLE_ENABLED, default) else default
fun setBleEnabled(value: Boolean) {
if (ready()) prefs.edit().putBoolean(KEY_BLE_ENABLED, value).apply()
}
fun getWifiAwareEnabled(default: Boolean = false): Boolean =
if (ready()) prefs.getBoolean(KEY_WIFI_AWARE_ENABLED, default) else default
fun setWifiAwareEnabled(value: Boolean) {
if (ready()) prefs.edit().putBoolean(KEY_WIFI_AWARE_ENABLED, value).apply()
}
fun getWifiAwareVerbose(default: Boolean = false): Boolean =
if (ready()) prefs.getBoolean(KEY_WIFI_AWARE_VERBOSE, default) else default
fun setWifiAwareVerbose(value: Boolean) {
if (ready()) prefs.edit().putBoolean(KEY_WIFI_AWARE_VERBOSE, value).apply()
}
} }
@@ -36,17 +36,6 @@ class DebugSettingsManager private constructor() {
private val _packetRelayEnabled = MutableStateFlow(true) private val _packetRelayEnabled = MutableStateFlow(true)
val packetRelayEnabled: StateFlow<Boolean> = _packetRelayEnabled.asStateFlow() val packetRelayEnabled: StateFlow<Boolean> = _packetRelayEnabled.asStateFlow()
// Master transport toggles
private val _bleEnabled = MutableStateFlow(true)
val bleEnabled: StateFlow<Boolean> = _bleEnabled.asStateFlow()
private val _wifiAwareEnabled = MutableStateFlow(false)
val wifiAwareEnabled: StateFlow<Boolean> = _wifiAwareEnabled.asStateFlow()
// Master transport toggles
private val _wifiAwareVerbose = MutableStateFlow(false)
val wifiAwareVerbose: StateFlow<Boolean> = _wifiAwareVerbose.asStateFlow()
// Visibility of the debug sheet; gates heavy work // Visibility of the debug sheet; gates heavy work
private val _debugSheetVisible = MutableStateFlow(false) private val _debugSheetVisible = MutableStateFlow(false)
val debugSheetVisible: StateFlow<Boolean> = _debugSheetVisible.asStateFlow() val debugSheetVisible: StateFlow<Boolean> = _debugSheetVisible.asStateFlow()
@@ -70,10 +59,6 @@ class DebugSettingsManager private constructor() {
_maxConnectionsOverall.value = DebugPreferenceManager.getMaxConnectionsOverall(8) _maxConnectionsOverall.value = DebugPreferenceManager.getMaxConnectionsOverall(8)
_maxServerConnections.value = DebugPreferenceManager.getMaxConnectionsServer(8) _maxServerConnections.value = DebugPreferenceManager.getMaxConnectionsServer(8)
_maxClientConnections.value = DebugPreferenceManager.getMaxConnectionsClient(8) _maxClientConnections.value = DebugPreferenceManager.getMaxConnectionsClient(8)
// Transport toggles
_bleEnabled.value = DebugPreferenceManager.getBleEnabled(true)
_wifiAwareEnabled.value = DebugPreferenceManager.getWifiAwareEnabled(false)
_wifiAwareVerbose.value = DebugPreferenceManager.getWifiAwareVerbose(false)
} catch (_: Exception) { } catch (_: Exception) {
// Preferences not ready yet; keep defaults. They will be applied on first change. // Preferences not ready yet; keep defaults. They will be applied on first change.
} }
@@ -277,27 +262,6 @@ class DebugSettingsManager private constructor() {
)) ))
} }
fun setBleEnabled(enabled: Boolean) {
DebugPreferenceManager.setBleEnabled(enabled)
_bleEnabled.value = enabled
addDebugMessage(DebugMessage.SystemMessage(if (enabled) "🟢 BLE enabled" else "🔴 BLE disabled"))
}
fun setWifiAwareEnabled(enabled: Boolean) {
DebugPreferenceManager.setWifiAwareEnabled(enabled)
_wifiAwareEnabled.value = enabled
addDebugMessage(DebugMessage.SystemMessage(if (enabled) "🟢 WiFi Aware enabled" else "🔴 WiFi Aware disabled"))
try {
com.bitchat.android.wifiaware.WifiAwareController.setEnabled(enabled)
} catch (_: Exception) { }
}
fun setWifiAwareVerbose(enabled: Boolean) {
DebugPreferenceManager.setWifiAwareVerbose(enabled)
_wifiAwareVerbose.value = enabled
addDebugMessage(DebugMessage.SystemMessage(if (enabled) "🔊 WiFi Aware verbose logging enabled" else "🔇 WiFi Aware verbose logging disabled"))
}
fun setMaxConnectionsOverall(value: Int) { fun setMaxConnectionsOverall(value: Int) {
val clamped = value.coerceIn(1, 32) val clamped = value.coerceIn(1, 32)
DebugPreferenceManager.setMaxConnectionsOverall(clamped) DebugPreferenceManager.setMaxConnectionsOverall(clamped)
@@ -355,16 +319,6 @@ class DebugSettingsManager private constructor() {
fun updateConnectedDevices(devices: List<ConnectedDevice>) { fun updateConnectedDevices(devices: List<ConnectedDevice>) {
_connectedDevices.value = devices _connectedDevices.value = devices
} }
// WiFi Aware debug collections
private val _wifiAwareDiscovered = MutableStateFlow<Map<String, String>>(emptyMap()) // peerID->nickname
val wifiAwareDiscovered: StateFlow<Map<String, String>> = _wifiAwareDiscovered.asStateFlow()
private val _wifiAwareConnected = MutableStateFlow<Map<String, String>>(emptyMap()) // peerID->ip
val wifiAwareConnected: StateFlow<Map<String, String>> = _wifiAwareConnected.asStateFlow()
fun updateWifiAwareDiscovered(map: Map<String, String>) { _wifiAwareDiscovered.value = map }
fun updateWifiAwareConnected(map: Map<String, String>) { _wifiAwareConnected.value = map }
fun updateRelayStats(stats: PacketRelayStats) { fun updateRelayStats(stats: PacketRelayStats) {
_relayStats.value = stats _relayStats.value = stats
@@ -10,8 +10,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bluetooth import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material.icons.filled.WifiTethering
import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Devices import androidx.compose.material.icons.filled.Devices
@@ -52,7 +50,7 @@ fun DebugSettingsSheet(
val verboseLogging by manager.verboseLoggingEnabled.collectAsState() val verboseLogging by manager.verboseLoggingEnabled.collectAsState()
val gattServerEnabled by manager.gattServerEnabled.collectAsState() val gattServerEnabled by manager.gattServerEnabled.collectAsState()
val gattClientEnabled by manager.gattClientEnabled.collectAsState() val gattClientEnabled by manager.gattClientEnabled.collectAsState()
val packetRelayed by manager.packetRelayEnabled.collectAsState() val packetRelayEnabled by manager.packetRelayEnabled.collectAsState()
val maxOverall by manager.maxConnectionsOverall.collectAsState() val maxOverall by manager.maxConnectionsOverall.collectAsState()
val maxServer by manager.maxServerConnections.collectAsState() val maxServer by manager.maxServerConnections.collectAsState()
val maxClient by manager.maxClientConnections.collectAsState() val maxClient by manager.maxClientConnections.collectAsState()
@@ -64,12 +62,6 @@ fun DebugSettingsSheet(
val gcsMaxBytes by manager.gcsMaxBytes.collectAsState() val gcsMaxBytes by manager.gcsMaxBytes.collectAsState()
val gcsFpr by manager.gcsFprPercent.collectAsState() val gcsFpr by manager.gcsFprPercent.collectAsState()
val context = LocalContext.current val context = LocalContext.current
val bleEnabled by manager.bleEnabled.collectAsState()
val wifiAwareEnabled by manager.wifiAwareEnabled.collectAsState()
val wifiAwareVerbose by manager.wifiAwareVerbose.collectAsState()
val wifiAwareDiscovered by manager.wifiAwareDiscovered.collectAsState()
val wifiAwareConnected by manager.wifiAwareConnected.collectAsState()
// Persistent notification is now controlled solely by MeshServicePreferences.isBackgroundEnabled // Persistent notification is now controlled solely by MeshServicePreferences.isBackgroundEnabled
// Push live connected devices from mesh service whenever sheet is visible // Push live connected devices from mesh service whenever sheet is visible
@@ -94,15 +86,6 @@ fun DebugSettingsSheet(
) )
} }
manager.updateConnectedDevices(devices) manager.updateConnectedDevices(devices)
// Also surface WiFi Aware status
try {
val ctrl = com.bitchat.android.wifiaware.WifiAwareController
val known = ctrl.knownPeers.value
val discovered = ctrl.discoveredPeers.value
val discoveredMap = discovered.associateWith { pid -> known[pid] ?: "" }
manager.updateWifiAwareDiscovered(discoveredMap)
manager.updateWifiAwareConnected(ctrl.connectedPeers.value)
} catch (_: Exception) { }
kotlinx.coroutines.delay(1000) kotlinx.coroutines.delay(1000)
} }
} }
@@ -230,46 +213,6 @@ fun DebugSettingsSheet(
} }
} }
// Transport toggles (BLE + WiFi Aware)
item {
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Filled.Devices, contentDescription = null, tint = Color(0xFF4CAF50))
Text("Transports", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Filled.Bluetooth, contentDescription = null, tint = Color(0xFF007AFF))
Spacer(Modifier.width(8.dp))
Text("BLE", fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
Switch(checked = bleEnabled, onCheckedChange = {
manager.setBleEnabled(it)
scope.launch {
if (it) {
if (gattServerEnabled) meshService.connectionManager.startServer()
if (gattClientEnabled) meshService.connectionManager.startClient()
} else {
meshService.connectionManager.stopServer()
meshService.connectionManager.stopClient()
}
}
})
}
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(Icons.Filled.Wifi, contentDescription = null, tint = Color(0xFF9C27B0))
Spacer(Modifier.width(8.dp))
Text("WiFi Aware", fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
Switch(checked = wifiAwareEnabled, onCheckedChange = { manager.setWifiAwareEnabled(it) })
}
Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(Modifier.width(24.dp))
Text("WiFi Aware verbose", fontFamily = FontFamily.Monospace, modifier = Modifier.weight(1f))
Switch(checked = wifiAwareVerbose, onCheckedChange = { manager.setWifiAwareVerbose(it) })
}
}
}
}
// Packet relay controls and stats // Packet relay controls and stats
item { item {
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) { Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
@@ -280,7 +223,7 @@ fun DebugSettingsSheet(
Icon(Icons.Filled.PowerSettingsNew, contentDescription = null, tint = Color(0xFFFF9500)) Icon(Icons.Filled.PowerSettingsNew, contentDescription = null, tint = Color(0xFFFF9500))
Text(stringResource(R.string.debug_packet_relay), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium) Text(stringResource(R.string.debug_packet_relay), fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
Switch(checked = packetRelayed, onCheckedChange = { manager.setPacketRelayEnabled(it) }) Switch(checked = packetRelayEnabled, onCheckedChange = { manager.setPacketRelayEnabled(it) })
} }
// Removed aggregate labels; we will show per-direction compact labels below titles // Removed aggregate labels; we will show per-direction compact labels below titles
// Toggle: overall vs per-connection vs per-peer // Toggle: overall vs per-connection vs per-peer
@@ -529,43 +472,6 @@ fun DebugSettingsSheet(
} }
} }
// WiFi Aware controls and status
item {
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Icon(Icons.Filled.WifiTethering, contentDescription = null, tint = Color(0xFF9C27B0))
Text("WiFi Aware", fontFamily = FontFamily.Monospace, fontSize = 14.sp, fontWeight = FontWeight.Medium)
Spacer(Modifier.weight(1f))
val running by com.bitchat.android.wifiaware.WifiAwareController.running.collectAsState()
Text(if (running) "running" else "stopped", fontFamily = FontFamily.Monospace, fontSize = 12.sp, color = colorScheme.onSurface.copy(alpha = 0.7f))
}
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
AssistChip(onClick = { com.bitchat.android.wifiaware.WifiAwareController.startIfPossible() }, label = { Text("Start") })
AssistChip(onClick = { com.bitchat.android.wifiaware.WifiAwareController.stop() }, label = { Text("Stop") })
AssistChip(onClick = { com.bitchat.android.wifiaware.WifiAwareController.getService()?.sendBroadcastAnnounce() }, label = { Text("Announce") })
}
Text("Discovered: ${wifiAwareDiscovered.size}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
if (wifiAwareDiscovered.isEmpty()) {
Text("No discoveries yet", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.6f))
} else {
wifiAwareDiscovered.entries.take(50).forEach { (peer, nick) ->
Text("${if (nick.isBlank()) peer.take(8) + "…" else nick} (${peer.take(8)}…) ", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
}
}
Divider()
Text("Connected: ${wifiAwareConnected.size}", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
if (wifiAwareConnected.isEmpty()) {
Text("No active sockets", fontFamily = FontFamily.Monospace, fontSize = 11.sp, color = colorScheme.onSurface.copy(alpha = 0.6f))
} else {
wifiAwareConnected.entries.take(50).forEach { (peer, ip) ->
Text("${peer.take(8)}… @ $ip", fontFamily = FontFamily.Monospace, fontSize = 12.sp)
}
}
}
}
}
// Connected devices // Connected devices
item { item {
Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) { Surface(shape = RoundedCornerShape(12.dp), color = colorScheme.surfaceVariant.copy(alpha = 0.2f)) {
@@ -1,116 +0,0 @@
package com.bitchat.android.wifiaware
import android.content.Context
import android.os.Build
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
/**
* WifiAwareController manages lifecycle and debug surfacing for the WifiAwareMeshService.
* It starts/stops the service based on debug preferences and exposes simple flows for UI.
*/
object WifiAwareController {
private const val TAG = "WifiAwareController"
private var service: WifiAwareMeshService? = null
private var appContext: Context? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val _enabled = MutableStateFlow(false)
val enabled: StateFlow<Boolean> = _enabled.asStateFlow()
private val _running = MutableStateFlow(false)
val running: StateFlow<Boolean> = _running.asStateFlow()
// Simple debug surfacing
private val _connectedPeers = MutableStateFlow<Map<String, String>>(emptyMap()) // peerID -> ip
val connectedPeers: StateFlow<Map<String, String>> = _connectedPeers.asStateFlow()
private val _knownPeers = MutableStateFlow<Map<String, String>>(emptyMap()) // peerID -> nickname
val knownPeers: StateFlow<Map<String, String>> = _knownPeers.asStateFlow()
private val _discoveredPeers = MutableStateFlow<Set<String>>(emptySet())
val discoveredPeers: StateFlow<Set<String>> = _discoveredPeers.asStateFlow()
fun initialize(context: Context, enabledByDefault: Boolean) {
appContext = context.applicationContext
setEnabled(enabledByDefault)
// Start background poller for debug surfacing
scope.launch {
while (isActive) {
try {
val s = service
if (s != null) {
_connectedPeers.value = s.getDeviceAddressToPeerMapping() // peerID -> ip
_knownPeers.value = s.getPeerNicknames()
_discoveredPeers.value = s.getDiscoveredPeerIds()
} else {
_connectedPeers.value = emptyMap()
_knownPeers.value = emptyMap()
_discoveredPeers.value = emptySet()
}
} catch (_: Exception) { }
delay(1000)
}
}
}
fun setEnabled(value: Boolean) {
_enabled.value = value
if (value) startIfPossible() else stop()
}
fun startIfPossible() {
if (_running.value) return
val ctx = appContext ?: return
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
Log.w(TAG, "WiFi Aware requires Android 10 (Q)+; disabled.")
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware not supported on this device (requires Android 10+)")) } catch (_: Exception) {}
return
}
// Android 13+: require NEARBY_WIFI_DEVICES runtime permission
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val granted = androidx.core.content.ContextCompat.checkSelfPermission(ctx, android.Manifest.permission.NEARBY_WIFI_DEVICES) == android.content.pm.PackageManager.PERMISSION_GRANTED
if (!granted) {
Log.w(TAG, "Missing NEARBY_WIFI_DEVICES permission; not starting WiFi Aware")
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("Grant Nearby WiFi Devices to start WiFi Aware")) } catch (_: Exception) {}
return
}
}
try {
service = WifiAwareMeshService(ctx).also {
it.startServices()
_running.value = true
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware started")) } catch (_: Exception) {}
}
} catch (e: Throwable) {
Log.e(TAG, "Failed to start WifiAwareMeshService", e)
_running.value = false
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware failed to start: ${e.message}")) } catch (_: Exception) {}
}
}
fun stop() {
try { service?.stopServices() } catch (_: Exception) { }
service = null
_running.value = false
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware stopped")) } catch (_: Exception) {}
}
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
}
File diff suppressed because it is too large Load Diff