mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 22:25:26 +00:00
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.
This commit is contained in:
@@ -39,6 +39,8 @@ import com.bitchat.android.ui.ChatScreen
|
|||||||
import com.bitchat.android.ui.ChatViewModel
|
import com.bitchat.android.ui.ChatViewModel
|
||||||
import com.bitchat.android.ui.OrientationAwareActivity
|
import com.bitchat.android.ui.OrientationAwareActivity
|
||||||
import com.bitchat.android.ui.theme.BitchatTheme
|
import com.bitchat.android.ui.theme.BitchatTheme
|
||||||
|
import com.bitchat.android.wifiaware.WifiAwareController
|
||||||
|
import com.bitchat.android.wifiaware.WifiAwareMeshDelegate
|
||||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -63,6 +65,37 @@ class MainActivity : OrientationAwareActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val wifiAwareDelegate by lazy {
|
||||||
|
object : WifiAwareMeshDelegate {
|
||||||
|
override fun didReceiveMessage(message: com.bitchat.android.model.BitchatMessage) {
|
||||||
|
chatViewModel.didReceiveMessage(message)
|
||||||
|
}
|
||||||
|
override fun didUpdatePeerList(peers: List<String>) {
|
||||||
|
chatViewModel.onWifiPeersUpdated(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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val forceFinishReceiver = object : android.content.BroadcastReceiver() {
|
private val forceFinishReceiver = object : android.content.BroadcastReceiver() {
|
||||||
override fun onReceive(context: android.content.Context, intent: android.content.Intent) {
|
override fun onReceive(context: android.content.Context, intent: android.content.Intent) {
|
||||||
if (intent.action == com.bitchat.android.util.AppConstants.UI.ACTION_FORCE_FINISH) {
|
if (intent.action == com.bitchat.android.util.AppConstants.UI.ACTION_FORCE_FINISH) {
|
||||||
@@ -166,44 +199,10 @@ class MainActivity : OrientationAwareActivity() {
|
|||||||
// Bridge Wi‑Fi Aware callbacks into ChatViewModel (reusing BLE delegate methods)
|
// Bridge Wi‑Fi Aware callbacks into ChatViewModel (reusing BLE delegate methods)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
com.bitchat.android.wifiaware.WifiAwareController.running.collect { running ->
|
WifiAwareController.running.collect { running ->
|
||||||
val svc = com.bitchat.android.wifiaware.WifiAwareController.getService()
|
val svc = WifiAwareController.getService()
|
||||||
if (running && svc != null) {
|
if (running && svc != null && lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||||
svc.delegate = object : com.bitchat.android.wifiaware.WifiAwareMeshDelegate {
|
svc.delegate = wifiAwareDelegate
|
||||||
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.onWifiPeersUpdated(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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -761,6 +760,7 @@ class MainActivity : OrientationAwareActivity() {
|
|||||||
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
if (mainViewModel.onboardingState.value == OnboardingState.COMPLETE) {
|
||||||
// Reattach mesh delegate to new ChatViewModel instance after Activity recreation
|
// Reattach mesh delegate to new ChatViewModel instance after Activity recreation
|
||||||
try { meshService.delegate = chatViewModel } catch (_: Exception) { }
|
try { meshService.delegate = chatViewModel } catch (_: Exception) { }
|
||||||
|
try { WifiAwareController.getService()?.delegate = wifiAwareDelegate } catch (_: Exception) { }
|
||||||
// Set app foreground state
|
// Set app foreground state
|
||||||
meshService.connectionManager.setAppBackgroundState(false)
|
meshService.connectionManager.setAppBackgroundState(false)
|
||||||
chatViewModel.setAppBackgroundState(false)
|
chatViewModel.setAppBackgroundState(false)
|
||||||
@@ -798,6 +798,7 @@ class MainActivity : OrientationAwareActivity() {
|
|||||||
chatViewModel.setAppBackgroundState(true)
|
chatViewModel.setAppBackgroundState(true)
|
||||||
// Detach UI delegate so the foreground service can own DM notifications while UI is closed
|
// Detach UI delegate so the foreground service can own DM notifications while UI is closed
|
||||||
try { meshService.delegate = null } catch (_: Exception) { }
|
try { meshService.delegate = null } catch (_: Exception) { }
|
||||||
|
try { WifiAwareController.getService()?.delegate = null } catch (_: Exception) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import com.bitchat.android.mesh.MeshService
|
|||||||
import com.bitchat.android.mesh.MeshTransport
|
import com.bitchat.android.mesh.MeshTransport
|
||||||
import com.bitchat.android.mesh.PeerInfo
|
import com.bitchat.android.mesh.PeerInfo
|
||||||
import com.bitchat.android.model.BitchatFilePacket
|
import com.bitchat.android.model.BitchatFilePacket
|
||||||
|
import com.bitchat.android.model.BitchatMessage
|
||||||
import com.bitchat.android.model.RoutedPacket
|
import com.bitchat.android.model.RoutedPacket
|
||||||
import com.bitchat.android.protocol.BitchatPacket
|
import com.bitchat.android.protocol.BitchatPacket
|
||||||
import com.bitchat.android.service.TransportBridgeService
|
import com.bitchat.android.service.TransportBridgeService
|
||||||
@@ -67,6 +68,13 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
private val wifiTransport = WifiAwareTransport()
|
private val wifiTransport = WifiAwareTransport()
|
||||||
private lateinit var meshCore: MeshCore
|
private lateinit var meshCore: MeshCore
|
||||||
|
|
||||||
|
// Service-level notification manager for background (no-UI) DMs
|
||||||
|
private val serviceNotificationManager = com.bitchat.android.ui.NotificationManager(
|
||||||
|
context.applicationContext,
|
||||||
|
androidx.core.app.NotificationManagerCompat.from(context.applicationContext),
|
||||||
|
com.bitchat.android.util.NotificationIntervalManager()
|
||||||
|
)
|
||||||
|
|
||||||
// Wi-Fi Aware transport
|
// Wi-Fi Aware transport
|
||||||
private val awareManager = context.getSystemService(WifiAwareManager::class.java)
|
private val awareManager = context.getSystemService(WifiAwareManager::class.java)
|
||||||
private var wifiAwareSession: WifiAwareSession? = null
|
private var wifiAwareSession: WifiAwareSession? = null
|
||||||
@@ -95,6 +103,9 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
private val lastTimestamps = ConcurrentHashMap<String, ULong>()
|
private val lastTimestamps = ConcurrentHashMap<String, ULong>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
// Ensure BluetoothMeshService is initialized so we share its GossipSyncManager
|
||||||
|
// This avoids race conditions and ensures a single gossip source/delegate
|
||||||
|
com.bitchat.android.service.MeshServiceHolder.getOrCreate(context)
|
||||||
val shared = com.bitchat.android.service.MeshServiceHolder.sharedGossipSyncManager
|
val shared = com.bitchat.android.service.MeshServiceHolder.sharedGossipSyncManager
|
||||||
meshCore = MeshCore(
|
meshCore = MeshCore(
|
||||||
context = context.applicationContext,
|
context = context.applicationContext,
|
||||||
@@ -110,6 +121,7 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
override fun gcsTargetFpr(): Double = 0.01
|
override fun gcsTargetFpr(): Double = 0.01
|
||||||
},
|
},
|
||||||
hooks = MeshCore.Hooks(
|
hooks = MeshCore.Hooks(
|
||||||
|
onMessageReceived = { message -> handleMessageReceived(message) },
|
||||||
onAnnounceProcessed = { routed, _ ->
|
onAnnounceProcessed = { routed, _ ->
|
||||||
routed.peerID?.let { pid ->
|
routed.peerID?.let { pid ->
|
||||||
try { meshCore.gossipSyncManager.scheduleInitialSyncToPeer(pid, 1_000) } catch (_: Exception) { }
|
try { meshCore.gossipSyncManager.scheduleInitialSyncToPeer(pid, 1_000) } catch (_: Exception) { }
|
||||||
@@ -125,6 +137,35 @@ class WifiAwareMeshService(private val context: Context) : MeshService, Transpor
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleMessageReceived(message: BitchatMessage) {
|
||||||
|
try {
|
||||||
|
when {
|
||||||
|
message.isPrivate -> {
|
||||||
|
val peer = message.senderPeerID ?: ""
|
||||||
|
if (peer.isNotEmpty()) com.bitchat.android.services.AppStateStore.addPrivateMessage(peer, message)
|
||||||
|
}
|
||||||
|
message.channel != null -> {
|
||||||
|
com.bitchat.android.services.AppStateStore.addChannelMessage(message.channel!!, message)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
com.bitchat.android.services.AppStateStore.addPublicMessage(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
|
||||||
|
if (delegate == null && message.isPrivate) {
|
||||||
|
try {
|
||||||
|
val senderPeerID = message.senderPeerID
|
||||||
|
if (senderPeerID != null) {
|
||||||
|
val nick = try { meshCore.getPeerNickname(senderPeerID) } catch (_: Exception) { null } ?: senderPeerID
|
||||||
|
val preview = com.bitchat.android.ui.NotificationTextUtils.buildPrivateMessagePreview(message)
|
||||||
|
serviceNotificationManager.setAppBackgroundState(true)
|
||||||
|
serviceNotificationManager.showPrivateMessageNotification(senderPeerID, nick, preview)
|
||||||
|
}
|
||||||
|
} catch (_: Exception) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcasts raw bytes to currently connected peer.
|
* Broadcasts raw bytes to currently connected peer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user