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,386 @@
package com.bitchat.android.mesh
import android.content.Context
import android.util.Log
import com.bitchat.android.model.BitchatFilePacket
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.noise.NoiseSession
import com.bitchat.android.wifiaware.WifiAwareController
/**
* Feature-facing mesh service that hides local transport selection from the rest of the app.
*
* BLE remains the canonical origin for broadcast packets when it is enabled so existing BLE mesh
* behavior and bridge semantics stay intact. Addressed Noise traffic is routed over whichever
* local transport already has the peer/session, falling back to a connected transport handshake.
*/
class UnifiedMeshService(
private val context: Context,
private val bluetooth: BluetoothMeshService
) : MeshService, BluetoothMeshDelegate {
companion object {
private const val TAG = "UnifiedMeshService"
}
override val myPeerID: String
get() = bluetooth.myPeerID
override var delegate: MeshDelegate? = null
set(value) {
field = value
refreshDelegates()
}
fun refreshDelegates() {
try { bluetooth.delegate = if (delegate != null) this else null } catch (_: Exception) { }
try { wifiService()?.delegate = if (delegate != null) this else null } catch (_: Exception) { }
}
override fun startServices() {
if (isBleEnabled()) {
try { bluetooth.startServices() } catch (e: Exception) {
Log.w(TAG, "Failed to start BLE transport: ${e.message}")
}
} else {
try { bluetooth.setBleTransportEnabled(false) } catch (_: Exception) { }
}
try { WifiAwareController.startIfPossible() } catch (e: Exception) {
Log.w(TAG, "Failed to start Wi-Fi Aware transport: ${e.message}")
}
refreshDelegates()
}
override fun stopServices() {
try { bluetooth.stopServices() } catch (_: Exception) { }
try { WifiAwareController.stop() } catch (_: Exception) { }
}
override fun sendMessage(content: String, mentions: List<String>, channel: String?) {
when {
isBleEnabled() -> bluetooth.sendMessage(content, mentions, channel)
else -> wifiService()?.sendMessage(content, mentions, channel)
}
}
override fun sendPrivateMessage(
content: String,
recipientPeerID: String,
recipientNickname: String,
messageID: String?
) {
when {
isBleReady(recipientPeerID) -> bluetooth.sendPrivateMessage(content, recipientPeerID, recipientNickname, messageID)
isWifiReady(recipientPeerID) -> wifiService()?.sendPrivateMessage(content, recipientPeerID, recipientNickname, messageID)
isBleConnected(recipientPeerID) || (isBleEnabled() && !isWifiConnected(recipientPeerID)) ->
bluetooth.sendPrivateMessage(content, recipientPeerID, recipientNickname, messageID)
else -> wifiService()?.sendPrivateMessage(content, recipientPeerID, recipientNickname, messageID)
}
}
override fun sendReadReceipt(messageID: String, recipientPeerID: String, readerNickname: String) {
when {
isBleReady(recipientPeerID) -> bluetooth.sendReadReceipt(messageID, recipientPeerID, readerNickname)
isWifiReady(recipientPeerID) -> wifiService()?.sendReadReceipt(messageID, recipientPeerID, readerNickname)
}
}
override fun sendFavoriteNotification(peerID: String, isFavorite: Boolean) {
val myNpub = try {
com.bitchat.android.nostr.NostrIdentityBridge.getCurrentNostrIdentity(context)?.npub
} catch (_: Exception) {
null
}
val content = if (isFavorite) "[FAVORITED]:${myNpub ?: ""}" else "[UNFAVORITED]:${myNpub ?: ""}"
val nickname = getPeerNicknames()[peerID] ?: peerID
if (hasEstablishedSession(peerID)) {
sendPrivateMessage(content, peerID, nickname, java.util.UUID.randomUUID().toString())
}
}
override fun sendVerifyChallenge(peerID: String, noiseKeyHex: String, nonceA: ByteArray) {
when {
isBleReady(peerID) -> bluetooth.sendVerifyChallenge(peerID, noiseKeyHex, nonceA)
isWifiReady(peerID) -> wifiService()?.sendVerifyChallenge(peerID, noiseKeyHex, nonceA)
}
}
override fun sendVerifyResponse(peerID: String, noiseKeyHex: String, nonceA: ByteArray) {
when {
isBleReady(peerID) -> bluetooth.sendVerifyResponse(peerID, noiseKeyHex, nonceA)
isWifiReady(peerID) -> wifiService()?.sendVerifyResponse(peerID, noiseKeyHex, nonceA)
}
}
override fun sendFileBroadcast(file: BitchatFilePacket) {
when {
isBleEnabled() -> bluetooth.sendFileBroadcast(file)
else -> wifiService()?.sendFileBroadcast(file)
}
}
override fun sendFilePrivate(recipientPeerID: String, file: BitchatFilePacket) {
when {
isBleReady(recipientPeerID) -> bluetooth.sendFilePrivate(recipientPeerID, file)
isWifiReady(recipientPeerID) -> wifiService()?.sendFilePrivate(recipientPeerID, file)
isBleConnected(recipientPeerID) || (isBleEnabled() && !isWifiConnected(recipientPeerID)) ->
bluetooth.sendFilePrivate(recipientPeerID, file)
else -> wifiService()?.sendFilePrivate(recipientPeerID, file)
}
}
override fun cancelFileTransfer(transferId: String): Boolean {
val bleCancelled = try { bluetooth.cancelFileTransfer(transferId) } catch (_: Exception) { false }
val wifiCancelled = try { wifiService()?.cancelFileTransfer(transferId) == true } catch (_: Exception) { false }
return bleCancelled || wifiCancelled
}
override fun sendBroadcastAnnounce() {
if (isBleEnabled()) {
try { bluetooth.sendBroadcastAnnounce() } catch (_: Exception) { }
}
try { wifiService()?.sendBroadcastAnnounce() } catch (_: Exception) { }
}
override fun sendAnnouncementToPeer(peerID: String) {
when {
isBleConnected(peerID) || (isBleEnabled() && !isWifiConnected(peerID)) -> bluetooth.sendAnnouncementToPeer(peerID)
else -> wifiService()?.sendAnnouncementToPeer(peerID)
}
}
override fun getPeerNicknames(): Map<String, String> {
val merged = linkedMapOf<String, String>()
try { merged.putAll(wifiService()?.getPeerNicknames().orEmpty()) } catch (_: Exception) { }
try { merged.putAll(bluetooth.getPeerNicknames()) } catch (_: Exception) { }
return merged
}
override fun getPeerRSSI(): Map<String, Int> {
val merged = linkedMapOf<String, Int>()
try { merged.putAll(wifiService()?.getPeerRSSI().orEmpty()) } catch (_: Exception) { }
try { merged.putAll(bluetooth.getPeerRSSI()) } catch (_: Exception) { }
return merged
}
override fun getActivePeerCount(): Int {
return mergedPeerIDs().filter { it != myPeerID }.distinct().size
}
override fun hasEstablishedSession(peerID: String): Boolean {
return isBleReady(peerID) || isWifiReady(peerID)
}
override fun getSessionState(peerID: String): NoiseSession.NoiseSessionState {
val bleState = try { bluetooth.getSessionState(peerID) } catch (_: Exception) { NoiseSession.NoiseSessionState.Uninitialized }
val wifiState = try { wifiService()?.getSessionState(peerID) } catch (_: Exception) { null }
return when {
bleState is NoiseSession.NoiseSessionState.Established -> bleState
wifiState is NoiseSession.NoiseSessionState.Established -> wifiState
bleState is NoiseSession.NoiseSessionState.Handshaking -> bleState
wifiState is NoiseSession.NoiseSessionState.Handshaking -> wifiState
bleState !is NoiseSession.NoiseSessionState.Uninitialized -> bleState
wifiState != null -> wifiState
else -> bleState
}
}
override fun initiateNoiseHandshake(peerID: String) {
when {
isBleConnected(peerID) -> bluetooth.initiateNoiseHandshake(peerID)
isWifiConnected(peerID) -> wifiService()?.initiateNoiseHandshake(peerID)
isBleEnabled() -> bluetooth.initiateNoiseHandshake(peerID)
else -> wifiService()?.initiateNoiseHandshake(peerID)
}
}
override fun getPeerFingerprint(peerID: String): String? {
return try { bluetooth.getPeerFingerprint(peerID) } catch (_: Exception) { null }
?: try { wifiService()?.getPeerFingerprint(peerID) } catch (_: Exception) { null }
}
override fun getPeerInfo(peerID: String): PeerInfo? {
val ble = try { bluetooth.getPeerInfo(peerID) } catch (_: Exception) { null }
val wifi = try { wifiService()?.getPeerInfo(peerID) } catch (_: Exception) { null }
return when {
ble?.isConnected == true && hasEstablishedSessionOnBluetooth(peerID) -> ble
wifi?.isConnected == true && wifiService()?.hasEstablishedSession(peerID) == true -> wifi
ble?.isConnected == true -> ble
wifi?.isConnected == true -> wifi
else -> ble ?: wifi
}
}
override fun updatePeerInfo(
peerID: String,
nickname: String,
noisePublicKey: ByteArray,
signingPublicKey: ByteArray,
isVerified: Boolean
): Boolean {
val bleUpdated = try {
bluetooth.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified)
} catch (_: Exception) {
false
}
val wifiUpdated = try {
wifiService()?.updatePeerInfo(peerID, nickname, noisePublicKey, signingPublicKey, isVerified) == true
} catch (_: Exception) {
false
}
return bleUpdated || wifiUpdated
}
override fun getIdentityFingerprint(): String = bluetooth.getIdentityFingerprint()
override fun getStaticNoisePublicKey(): ByteArray? {
return bluetooth.getStaticNoisePublicKey() ?: wifiService()?.getStaticNoisePublicKey()
}
override fun shouldShowEncryptionIcon(peerID: String): Boolean {
return hasEstablishedSession(peerID)
}
override fun getEncryptedPeers(): List<String> {
val encrypted = linkedSetOf<String>()
try { encrypted.addAll(bluetooth.getEncryptedPeers()) } catch (_: Exception) { }
try { encrypted.addAll(wifiService()?.getEncryptedPeers().orEmpty()) } catch (_: Exception) { }
mergedPeerIDs().filterTo(encrypted) { hasEstablishedSession(it) }
return encrypted.toList()
}
override fun getDeviceAddressForPeer(peerID: String): String? {
return try { bluetooth.getDeviceAddressForPeer(peerID) } catch (_: Exception) { null }
?: try { wifiService()?.getDeviceAddressForPeer(peerID) } catch (_: Exception) { null }
}
override fun getDeviceAddressToPeerMapping(): Map<String, String> {
val merged = linkedMapOf<String, String>()
try { merged.putAll(wifiService()?.getDeviceAddressToPeerMapping().orEmpty()) } catch (_: Exception) { }
try { merged.putAll(bluetooth.getDeviceAddressToPeerMapping()) } catch (_: Exception) { }
return merged
}
override fun printDeviceAddressesForPeers(): String {
return buildString {
appendLine(bluetooth.printDeviceAddressesForPeers())
wifiService()?.let {
appendLine()
appendLine(it.printDeviceAddressesForPeers())
}
}
}
override fun getDebugStatus(): String {
return buildString {
appendLine("=== Unified Mesh Service Debug Status ===")
appendLine("My Peer ID: $myPeerID")
appendLine("Merged Peers: ${mergedPeerIDs().joinToString(", ")}")
appendLine()
appendLine(bluetooth.getDebugStatus())
wifiService()?.let {
appendLine()
appendLine(it.getDebugStatus())
}
}
}
override fun clearAllInternalData() {
try { bluetooth.clearAllInternalData() } catch (_: Exception) { }
try { wifiService()?.clearAllInternalData() } catch (_: Exception) { }
}
override fun clearAllEncryptionData() {
try { bluetooth.clearAllEncryptionData() } catch (_: Exception) { }
try { wifiService()?.clearAllEncryptionData() } catch (_: Exception) { }
}
override fun didReceiveMessage(message: BitchatMessage) {
delegate?.didReceiveMessage(message)
}
override fun didUpdatePeerList(peers: List<String>) {
delegate?.didUpdatePeerList(mergedPeerIDs().ifEmpty { peers.distinct() })
}
override fun didReceiveChannelLeave(channel: String, fromPeer: String) {
delegate?.didReceiveChannelLeave(channel, fromPeer)
}
override fun didReceiveDeliveryAck(messageID: String, recipientPeerID: String) {
delegate?.didReceiveDeliveryAck(messageID, recipientPeerID)
}
override fun didReceiveReadReceipt(messageID: String, recipientPeerID: String) {
delegate?.didReceiveReadReceipt(messageID, recipientPeerID)
}
override fun didReceiveVerifyChallenge(peerID: String, payload: ByteArray, timestampMs: Long) {
delegate?.didReceiveVerifyChallenge(peerID, payload, timestampMs)
}
override fun didReceiveVerifyResponse(peerID: String, payload: ByteArray, timestampMs: Long) {
delegate?.didReceiveVerifyResponse(peerID, payload, timestampMs)
}
override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? {
return delegate?.decryptChannelMessage(encryptedContent, channel)
}
override fun getNickname(): String? = delegate?.getNickname()
override fun isFavorite(peerID: String): Boolean = delegate?.isFavorite(peerID) ?: false
private fun mergedPeerIDs(): List<String> {
val ids = linkedSetOf<String>()
try { ids.addAll(com.bitchat.android.services.AppStateStore.peers.value) } catch (_: Exception) { }
try { ids.addAll(bluetooth.getPeerNicknames().keys) } catch (_: Exception) { }
try { ids.addAll(wifiService()?.getPeerNicknames()?.keys.orEmpty()) } catch (_: Exception) { }
return ids.toList()
}
private fun wifiService(): MeshService? {
return try {
WifiAwareController.getService()?.also { service ->
if (delegate != null && service.delegate !== this) {
service.delegate = this
}
}
} catch (_: Exception) {
null
}
}
private fun isBleEnabled(): Boolean {
return try {
com.bitchat.android.ui.debug.DebugSettingsManager.getInstance().bleEnabled.value
} catch (_: Exception) {
try { com.bitchat.android.ui.debug.DebugPreferenceManager.getBleEnabled(true) } catch (_: Exception) { true }
}
}
private fun isBleConnected(peerID: String): Boolean {
return try { bluetooth.getPeerInfo(peerID)?.isConnected == true } catch (_: Exception) { false }
}
private fun isWifiConnected(peerID: String): Boolean {
return try { wifiService()?.getPeerInfo(peerID)?.isConnected == true } catch (_: Exception) { false }
}
private fun isBleReady(peerID: String): Boolean {
return isBleConnected(peerID) && hasEstablishedSessionOnBluetooth(peerID)
}
private fun isWifiReady(peerID: String): Boolean {
return try {
val wifi = wifiService()
wifi?.getPeerInfo(peerID)?.isConnected == true && wifi.hasEstablishedSession(peerID)
} catch (_: Exception) {
false
}
}
private fun hasEstablishedSessionOnBluetooth(peerID: String): Boolean {
return try { bluetooth.hasEstablishedSession(peerID) } catch (_: Exception) { false }
}
}