Wifi aware refactor mesh core (#711)

* wifi aware wip

* wifi aware wip

* starting to work

* werk

* dms work

* wip

* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork

* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding

* Fix Wi-Fi Aware connectivity and UI integration post-merge

- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.

* Enhance Wi-Fi Aware robustness and debug UI display

- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.

* Share GossipSyncManager across transports to prevent redundant message synchronization

- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.

* Restore VPN acquisition logic and improve peer cleanup

- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.

* Fix wifi aware socket binding 2 (#536)

* Background persistence (#505)

* persistence step 1

* fix build

* messages in the background work, notifications not yet

* app state store

* DM icon shows up

* notification launches when app is closed!

* keep ui updated

* lifecycle fixes

* extensive logging, maybe revert later

* send nickname in announcement

* quit in notification

* setting in about sheet

* fix quit bitchat

* lifecycle fixes

* power mode based on background state

* stats for both direciotns

* fix graph persistence

* better counting

* count per device

* only compute when debug sheet is open? untested

* fix read receipts

* fix read receipts fully

* fix unread badge if messages have been read in focus

* foreground promotion fix

* fix app kill in notification

* adjust to new tor

* nice

* about sheet design

* bump version 1.6.0 (#524)

* Automated update of relay data - Sun Dec 14 06:06:53 UTC 2025

* bump targetSdk (#526)

* Automated update of relay data - Sun Dec 21 06:06:56 UTC 2025

* Automated update of relay data - Sun Dec 28 06:07:12 UTC 2025

* Prevent quit notification from reappearing (#530)

* shutdown sequence

* Prevent quit notification from reappearing

* Restrict force-finish broadcast

* Cancel quit shutdown on relaunch

* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork

* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding

* Fix Wi-Fi Aware connectivity and UI integration post-merge

- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.

* Enhance Wi-Fi Aware robustness and debug UI display

- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.

* Share GossipSyncManager across transports to prevent redundant message synchronization

- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.

* Restore VPN acquisition logic and improve peer cleanup

- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalue <>

* Fix wifi aware routing (#534)

* Background persistence (#505)

* persistence step 1

* fix build

* messages in the background work, notifications not yet

* app state store

* DM icon shows up

* notification launches when app is closed!

* keep ui updated

* lifecycle fixes

* extensive logging, maybe revert later

* send nickname in announcement

* quit in notification

* setting in about sheet

* fix quit bitchat

* lifecycle fixes

* power mode based on background state

* stats for both direciotns

* fix graph persistence

* better counting

* count per device

* only compute when debug sheet is open? untested

* fix read receipts

* fix read receipts fully

* fix unread badge if messages have been read in focus

* foreground promotion fix

* fix app kill in notification

* adjust to new tor

* nice

* about sheet design

* bump version 1.6.0 (#524)

* Automated update of relay data - Sun Dec 14 06:06:53 UTC 2025

* bump targetSdk (#526)

* Automated update of relay data - Sun Dec 21 06:06:56 UTC 2025

* Automated update of relay data - Sun Dec 28 06:07:12 UTC 2025

* Prevent quit notification from reappearing (#530)

* shutdown sequence

* Prevent quit notification from reappearing

* Restrict force-finish broadcast

* Cancel quit shutdown on relaunch

* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork

* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding

* Fix Wi-Fi Aware connectivity and UI integration post-merge

- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.

* Enhance Wi-Fi Aware robustness and debug UI display

- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.

* Share GossipSyncManager across transports to prevent redundant message synchronization

- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.

* Restore VPN acquisition logic and improve peer cleanup

- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.

* fix wifi aware routing

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalue <>

* unify connection tracker for ble and wifi (#537)

* unify connection tracker for ble and wifi

* cleanup nicely

* Wifi aware skip bluetooth (#538)

* unify connection tracker for ble and wifi

* bluetooth optional

* refactor mesh core

* tests

* share gossip

* refresh peer list more often

* patches: wifi aware mesh refactor core (#549)

* Refactor WifiAware memory management and lifecycle handling

* Fix Wi-Fi Aware reconnection by ensuring callback unregistration and session watchdog

* fix(wifiaware): allow WifiAwareController to restart service if session drops

* Enhance WiFi Aware logging with network request timeouts and detailed callback status

* Ensure explicit release of WiFi Aware network callbacks on failure or disconnection

---------

Co-authored-by: aidenvalue <>

* fix: wifi aware mesh private dms (#561)

* fix(wifi-aware): restore peer check and remove aggressive session restarts

* synchronized access to sockets.

* clear old peer socket `onClientConnected` if one was found.

* address second round of review

* fix: Restart publish session on termination in WifiAwareMeshService

* fix: cleanup connection tracker resources when handling peer disconnection without active socket

---------

Co-authored-by: aidenvalu3 <>

* fix(mesh): address memory leaks and background persistence issues in Wi-Fi Aware

- Move message persistence and background notifications for Wi-Fi Aware into the service layer via MeshCore hooks.
- Fix memory leak in MainActivity by properly detaching WifiAwareMeshDelegate on pause.
- Ensure BluetoothMeshService is initialized before WifiAwareMeshService to guarantee shared GossipSyncManager instance.
- Deduplicate persistence logic from MainActivity delegate.

* wifi aware working

* more robust

* fragments work

* new files

* wifi improvements

* handshake works

* improve

* wifi mesh more robust

* fix review

* check for support

* wifi aware fixes

---------

Co-authored-by: aidenvalue <>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalu3 <erendentman@gmail.com>
Co-authored-by: a1denvalu3 <43107113+a1denvalu3@users.noreply.github.com>
Co-authored-by: CC <cc@ggg.local>
This commit is contained in:
callebtc
2026-06-16 12:11:33 -05:00
committed by GitHub
co-authored by aidenvalue <> GitHub Action aidenvalu3 a1denvalu3 CC
parent 6189f0cb84
commit 3a983c5767
99 changed files with 5982 additions and 649 deletions
@@ -0,0 +1,305 @@
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
import java.util.concurrent.atomic.AtomicBoolean
/**
* 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 const val MAX_RESTART_ATTEMPTS = 15
private const val RESTART_RETRY_DELAY_MS = 2_000L
private var service: WifiAwareMeshService? = null
private var appContext: Context? = null
private val lifecycleLock = Any()
private var starting = false
private val restartInFlight = AtomicBoolean(false)
private var awareReceiverRegistered = false
private var lastBlockedReason: String? = null
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val _enabled = MutableStateFlow(false)
val enabled: StateFlow<Boolean> = _enabled.asStateFlow()
private val _supported = MutableStateFlow(false)
val supported: StateFlow<Boolean> = _supported.asStateFlow()
private val _available = MutableStateFlow(false)
val available: StateFlow<Boolean> = _available.asStateFlow()
private val _supportStatus = MutableStateFlow<WifiAwareSupport.Status?>(null)
val supportStatus: StateFlow<WifiAwareSupport.Status?> = _supportStatus.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
val status = refreshSupportStatus(appContext!!)
if (status.supported) {
registerAwareStateReceiver(appContext!!)
} else {
Log.i(TAG, "Wi-Fi Aware unsupported: ${status.reason}")
}
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() {
val reusableService = synchronized(lifecycleLock) {
if (!_enabled.value) return
val existing = service
if (existing?.isRunning() == true) {
_running.value = true
return
}
if (starting) return
starting = true
existing
}
val ctx = appContext ?: run {
synchronized(lifecycleLock) { starting = false }
return
}
val status = refreshSupportStatus(ctx)
if (!status.supported) {
val reason = status.reason ?: "not supported"
Log.w(TAG, "WiFi Aware unsupported; not starting ($reason)")
addBlockedDebugMessage("unsupported:$reason", "Wi-Fi Aware not supported on this device ($reason)")
synchronized(lifecycleLock) { starting = false }
return
}
val awareManager = WifiAwareSupport.getManager(ctx)
if (awareManager == null || !status.available) {
Log.w(TAG, "Wi-Fi Aware is not currently available; not starting")
addBlockedDebugMessage("unavailable", "Wi-Fi Aware is not available right now")
synchronized(lifecycleLock) { starting = false }
return
}
// Check system location setting: WifiAwareManager.attach() throws SecurityException if disabled
val lm = ctx.getSystemService(Context.LOCATION_SERVICE) as? android.location.LocationManager
val locationEnabled = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lm?.isLocationEnabled == true
} else {
@Suppress("DEPRECATION")
lm?.isProviderEnabled(android.location.LocationManager.GPS_PROVIDER) == true ||
lm?.isProviderEnabled(android.location.LocationManager.NETWORK_PROVIDER) == true
}
if (!locationEnabled) {
Log.w(TAG, "Location services are disabled; Wi-Fi Aware cannot start.")
addBlockedDebugMessage("location-disabled", "Enable Location Services to start Wi-Fi Aware")
synchronized(lifecycleLock) { starting = false }
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")
addBlockedDebugMessage("missing-nearby-wifi", "Grant Nearby Wi-Fi Devices to start Wi-Fi Aware")
synchronized(lifecycleLock) { starting = false }
return
}
}
if (!_enabled.value) {
synchronized(lifecycleLock) { starting = false }
return
}
try {
val startedService = reusableService ?: run {
Log.i(TAG, "Instantiating WifiAwareMeshService...")
WifiAwareMeshService(ctx)
}
startedService.startServices()
if (startedService.isRunning()) {
synchronized(lifecycleLock) {
service = startedService
_running.value = true
}
try { com.bitchat.android.service.MeshServiceHolder.unifiedMeshService?.refreshDelegates() } catch (_: Exception) { }
clearBlockedDebugMessage()
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware started")) } catch (_: Exception) {}
} else {
if (reusableService == null) {
try { startedService.stopServices() } catch (_: Exception) { }
}
synchronized(lifecycleLock) {
if (service === startedService) service = null
_running.value = false
}
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware did not start")) } 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) {}
} finally {
synchronized(lifecycleLock) { starting = false }
}
}
fun stop() {
val stopped = synchronized(lifecycleLock) {
val current = service
service = null
starting = false
_running.value = false
current
}
try { stopped?.stopServices() } catch (_: Exception) { }
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
_connectedPeers.value = emptyMap()
_knownPeers.value = emptyMap()
_discoveredPeers.value = emptySet()
try { com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage("WiFi Aware stopped")) } catch (_: Exception) {}
}
internal fun onServiceStopped(stoppedService: WifiAwareMeshService) {
synchronized(lifecycleLock) {
if (service !== stoppedService) return
service = null
_running.value = false
try { com.bitchat.android.services.AppStateStore.clearTransportPeers("WIFI") } catch (_: Exception) { }
_connectedPeers.value = emptyMap()
_knownPeers.value = emptyMap()
_discoveredPeers.value = emptySet()
}
}
/**
* Schedules a restart of the Wi-Fi Aware transport. Concurrent requests are coalesced into
* a single in-flight loop that retries with backoff. This is important because a single fixed
* delay can land while the service is still tearing down (recoveryInProgress), in which case
* startServices() defers and we must try again rather than give up.
*/
internal fun restartIfStillEnabled(delayMs: Long = 0L) {
if (!restartInFlight.compareAndSet(false, true)) {
Log.d(TAG, "Restart already in flight; coalescing request")
return
}
scope.launch {
try {
if (delayMs > 0L) delay(delayMs)
var attempt = 0
while (_enabled.value && !_running.value && attempt < MAX_RESTART_ATTEMPTS) {
val ctx = appContext
if (ctx != null && !refreshSupportStatus(ctx).supported) break
startIfPossible()
if (_running.value) break
attempt++
delay(RESTART_RETRY_DELAY_MS)
}
} finally {
restartInFlight.set(false)
}
}
}
/**
* Listens for system Wi-Fi Aware availability changes. Aware can flip off/on at runtime
* (Wi-Fi toggling, hotspot/SoftAP, location changes); without this we would only recover on
* an unrelated trigger.
*/
private fun registerAwareStateReceiver(ctx: Context) {
if (awareReceiverRegistered) return
if (!refreshSupportStatus(ctx).supported) return
try {
val filter = android.content.IntentFilter(
android.net.wifi.aware.WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED
)
ctx.registerReceiver(object : android.content.BroadcastReceiver() {
override fun onReceive(c: Context?, intent: android.content.Intent?) {
val status = refreshSupportStatus(ctx)
Log.i(TAG, "Wi-Fi Aware availability changed: supported=${status.supported} available=${status.available} enabled=${_enabled.value} running=${_running.value}")
if (status.available) {
if (_enabled.value) restartIfStillEnabled(500)
} else if (_running.value) {
// Aware went away; tear down cleanly so we can re-attach when it returns.
// Note: this does not change the enabled preference.
stop()
}
}
}, filter)
awareReceiverRegistered = true
} catch (e: Exception) {
Log.w(TAG, "Failed to register Wi-Fi Aware state receiver: ${e.message}")
}
}
private fun refreshSupportStatus(ctx: Context): WifiAwareSupport.Status {
val status = WifiAwareSupport.evaluate(ctx)
_supported.value = status.supported
_available.value = status.available
_supportStatus.value = status
return status
}
private fun addBlockedDebugMessage(key: String, message: String) {
if (lastBlockedReason == key) return
lastBlockedReason = key
try {
com.bitchat.android.ui.debug.DebugSettingsManager.getInstance()
.addDebugMessage(com.bitchat.android.ui.debug.DebugMessage.SystemMessage(message))
} catch (_: Exception) { }
}
private fun clearBlockedDebugMessage() {
lastBlockedReason = null
}
fun getService(): WifiAwareMeshService? = service
}