Skip to content

Commit 73e576f

Browse files
committed
refactor: inject node service state
1 parent 500285d commit 73e576f

5 files changed

Lines changed: 36 additions & 15 deletions

File tree

app/src/main/java/to/bitkit/androidServices/LightningNodeService.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import to.bitkit.models.NewTransactionSheetDetails
3535
import to.bitkit.models.NotificationDetails
3636
import to.bitkit.repositories.LightningRepo
3737
import to.bitkit.repositories.WalletRepo
38+
import to.bitkit.services.NodeServiceState
3839
import to.bitkit.ui.ID_NOTIFICATION_NODE
3940
import to.bitkit.ui.MainActivity
4041
import to.bitkit.ui.pushNotification
@@ -74,6 +75,9 @@ class LightningNodeService : Service() {
7475
@Inject
7576
lateinit var appWidgetRefreshScheduler: AppWidgetRefreshScheduler
7677

78+
@Inject
79+
lateinit var nodeServiceState: NodeServiceState
80+
7781
private var hasStartedNode = false
7882

7983
private fun setupService() {
@@ -185,7 +189,7 @@ class LightningNodeService : Service() {
185189
}
186190

187191
ACTION_START_SERVICE -> if (promoteToForeground(startId)) {
188-
isRunning = true
192+
nodeServiceState.setForegroundServiceRunning(true)
189193
setupService()
190194
}
191195
else -> stop(startId) { Logger.warn("Stopped service for unsupported action '$action'", context = TAG) }
@@ -227,7 +231,7 @@ class LightningNodeService : Service() {
227231

228232
override fun onDestroy() {
229233
Logger.debug("onDestroy", context = TAG)
230-
isRunning = false
234+
nodeServiceState.setForegroundServiceRunning(false)
231235
// Safe to call even if already stopped — guarded by lifecycleMutex + isStoppedOrStopping()
232236
serviceScope.launch { lightningRepo.stop() }
233237
super.onDestroy()
@@ -244,10 +248,6 @@ class LightningNodeService : Service() {
244248
override fun onBind(intent: Intent?): IBinder? = null
245249

246250
companion object {
247-
@Volatile
248-
var isRunning = false
249-
internal set
250-
251251
const val CHANNEL_ID_NODE = "bitkit_notification_channel_node"
252252
const val TAG = "LightningNodeService"
253253
const val ACTION_START_SERVICE = "to.bitkit.androidServices.action.START_SERVICE"

app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import kotlinx.serialization.json.jsonObject
1818
import org.lightningdevkit.ldknode.Event
1919
import to.bitkit.App
2020
import to.bitkit.R
21-
import to.bitkit.androidServices.LightningNodeService
2221
import to.bitkit.data.CacheStore
2322
import to.bitkit.di.json
2423
import to.bitkit.domain.commands.ReceivedNotificationContent
@@ -38,6 +37,7 @@ import to.bitkit.models.msatCeilOf
3837
import to.bitkit.repositories.ActivityRepo
3938
import to.bitkit.repositories.BlocktankRepo
4039
import to.bitkit.repositories.LightningRepo
40+
import to.bitkit.services.NodeServiceState
4141
import to.bitkit.ui.pushNotification
4242
import to.bitkit.utils.Logger
4343
import to.bitkit.utils.measured
@@ -54,6 +54,7 @@ class WakeNodeWorker @AssistedInject constructor(
5454
private val activityRepo: ActivityRepo,
5555
private val cacheStore: CacheStore,
5656
private val receivedNotificationContent: ReceivedNotificationContent,
57+
private val nodeServiceState: NodeServiceState,
5758
) : CoroutineWorker(appContext, workerParams) {
5859
private var bestAttemptContent: NotificationDetails? = null
5960

@@ -268,7 +269,7 @@ class WakeNodeWorker @AssistedInject constructor(
268269
}
269270

270271
private fun isHandledInProcess(): Boolean =
271-
App.currentActivity?.value != null || LightningNodeService.isRunning
272+
App.currentActivity?.value != null || nodeServiceState.isForegroundServiceRunning
272273

273274
private suspend fun deliver() {
274275
// Send notification first
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package to.bitkit.services
2+
3+
import javax.inject.Inject
4+
import javax.inject.Singleton
5+
6+
/**
7+
* Process-wide flag tracking whether [to.bitkit.androidServices.LightningNodeService] is running as a
8+
* foreground service. Used to coordinate notification ownership: when the foreground service is alive
9+
* it handles LDK events in-process, so the background [to.bitkit.fcm.WakeNodeWorker] defers to it
10+
* instead of posting its own (duplicate) notification.
11+
*/
12+
@Singleton
13+
class NodeServiceState @Inject constructor() {
14+
@Volatile
15+
var isForegroundServiceRunning = false
16+
private set
17+
18+
fun setForegroundServiceRunning(running: Boolean) {
19+
isForegroundServiceRunning = running
20+
}
21+
}

app/src/test/java/to/bitkit/androidServices/LightningNodeServiceTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ class LightningNodeServiceTest : BaseUnitTest() {
170170
@After
171171
fun tearDown() {
172172
App.currentActivity = null
173-
LightningNodeService.isRunning = false
174173
}
175174

176175
@Test

app/src/test/java/to/bitkit/fcm/WakeNodeWorkerTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import org.robolectric.annotation.Config
3030
import to.bitkit.App
3131
import to.bitkit.CurrentActivity
3232
import to.bitkit.R
33-
import to.bitkit.androidServices.LightningNodeService
3433
import to.bitkit.data.CacheStore
3534
import to.bitkit.domain.commands.ReceivedNotificationContent
3635
import to.bitkit.ext.createChannelDetails
@@ -42,6 +41,7 @@ import to.bitkit.repositories.ActivityRepo
4241
import to.bitkit.repositories.BlocktankRepo
4342
import to.bitkit.repositories.LightningRepo
4443
import to.bitkit.services.NodeEventHandler
44+
import to.bitkit.services.NodeServiceState
4545
import to.bitkit.test.BaseUnitTest
4646
import kotlin.test.assertEquals
4747
import kotlin.test.assertNotNull
@@ -57,6 +57,7 @@ class WakeNodeWorkerTest : BaseUnitTest() {
5757
private val activityRepo = mock<ActivityRepo>()
5858
private val cacheStore = mock<CacheStore>()
5959
private val receivedNotificationContent = mock<ReceivedNotificationContent>()
60+
private val nodeServiceState = NodeServiceState()
6061

6162
private val channelId = "channel-1"
6263
private val receivedTitle by lazy { context.getString(R.string.notification__received__title) }
@@ -70,15 +71,13 @@ class WakeNodeWorkerTest : BaseUnitTest() {
7071
val app = context as Application
7172
Shadows.shadowOf(app).grantPermissions(Manifest.permission.POST_NOTIFICATIONS)
7273

73-
// Default: app killed (no foreground activity), no foreground service running
74+
// Default: app killed (no foreground activity); nodeServiceState defaults to not-running
7475
App.currentActivity = CurrentActivity()
75-
LightningNodeService.isRunning = false
7676
}
7777

7878
@After
7979
fun tearDown() {
8080
App.currentActivity = null
81-
LightningNodeService.isRunning = false
8281
}
8382

8483
@Test
@@ -114,7 +113,7 @@ class WakeNodeWorkerTest : BaseUnitTest() {
114113

115114
@Test
116115
fun `cjit channel ready skips notification when foreground service is running`() = test {
117-
LightningNodeService.isRunning = true
116+
nodeServiceState.setForegroundServiceRunning(true)
118117
val channel = cjitChannel(sats = 48_064)
119118
stubChannel(channel, cjitEntry = IcJitEntry.mock())
120119
stubStartFiring(channelReadyEvent())
@@ -179,7 +178,7 @@ class WakeNodeWorkerTest : BaseUnitTest() {
179178

180179
@Test
181180
fun `payment received skips notification when foreground service is running`() = test {
182-
LightningNodeService.isRunning = true
181+
nodeServiceState.setForegroundServiceRunning(true)
183182
whenever(workerParams.inputData).thenReturn(
184183
workDataOf("type" to BlocktankNotificationType.incomingHtlc.name),
185184
)
@@ -205,6 +204,7 @@ class WakeNodeWorkerTest : BaseUnitTest() {
205204
activityRepo = activityRepo,
206205
cacheStore = cacheStore,
207206
receivedNotificationContent = receivedNotificationContent,
207+
nodeServiceState = nodeServiceState,
208208
)
209209

210210
private fun channelReadyEvent() = mock<Event.ChannelReady> {

0 commit comments

Comments
 (0)