Skip to content

Commit b623227

Browse files
authored
Merge pull request #124 from synonymdev/feat/keep-node-in-background
Keep node running when the app is in background
2 parents f45a2c8 + 3a04a15 commit b623227

File tree

18 files changed

+1892
-588
lines changed

18 files changed

+1892
-588
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
<uses-permission android:name="android.permission.INTERNET" />
1212
<uses-permission android:name="android.permission.CAMERA" />
1313
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
14+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
15+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
1416

1517
<application
1618
android:name=".App"
@@ -20,6 +22,12 @@
2022
android:roundIcon="@mipmap/ic_launcher_regtest_round"
2123
android:supportsRtl="true"
2224
android:theme="@style/Theme.App">
25+
<service
26+
android:name=".androidServices.LightningNodeService"
27+
android:foregroundServiceType="dataSync"
28+
android:enabled="true"
29+
android:exported="false"/>
30+
2331
<activity
2432
android:name=".ui.MainActivity"
2533
android:exported="true"
@@ -28,6 +36,7 @@
2836
android:windowSoftInputMode="adjustResize|stateHidden">
2937
<intent-filter>
3038
<action android:name="android.intent.action.MAIN" />
39+
3140
<category android:name="android.intent.category.LAUNCHER" />
3241
</intent-filter>
3342
</activity>

app/src/main/java/to/bitkit/App.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ internal open class App : Application(), Configuration.Provider {
2424
override fun onCreate() {
2525
super.onCreate()
2626
currentActivity = CurrentActivity().also { registerActivityLifecycleCallbacks(it) }
27-
2827
Env.initAppStoragePath(filesDir.absolutePath)
2928
}
3029

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package to.bitkit.androidServices
2+
3+
import android.app.Notification
4+
import android.app.PendingIntent
5+
import android.app.Service
6+
import android.content.Intent
7+
import android.os.IBinder
8+
import androidx.core.app.NotificationCompat
9+
import dagger.hilt.android.AndroidEntryPoint
10+
import kotlinx.coroutines.CoroutineScope
11+
import kotlinx.coroutines.Dispatchers
12+
import kotlinx.coroutines.SupervisorJob
13+
import kotlinx.coroutines.cancel
14+
import kotlinx.coroutines.launch
15+
import to.bitkit.App
16+
import to.bitkit.R
17+
import to.bitkit.repositories.LightningRepo
18+
import to.bitkit.repositories.WalletRepo
19+
import to.bitkit.ui.MainActivity
20+
import to.bitkit.utils.Logger
21+
import javax.inject.Inject
22+
23+
@AndroidEntryPoint
24+
class LightningNodeService : Service() {
25+
26+
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
27+
28+
@Inject
29+
lateinit var lightningRepo: LightningRepo
30+
31+
@Inject
32+
lateinit var walletRepo: WalletRepo
33+
34+
override fun onCreate() {
35+
super.onCreate()
36+
startForeground(NOTIFICATION_ID, createNotification())
37+
setupService()
38+
}
39+
40+
private fun setupService() {
41+
serviceScope.launch {
42+
launch {
43+
lightningRepo.start(
44+
eventHandler = { event ->
45+
walletRepo.refreshBip21ForEvent(event)
46+
}
47+
).onSuccess {
48+
val notification = createNotification()
49+
startForeground(NOTIFICATION_ID, notification)
50+
51+
walletRepo.setWalletExistsState()
52+
walletRepo.refreshBip21()
53+
walletRepo.syncBalances()
54+
}
55+
}
56+
}
57+
}
58+
59+
// Update the createNotification method in LightningNodeService.kt
60+
private fun createNotification(
61+
contentText: String = "Bitkit is running in background so you can receive Lightning payments"
62+
): Notification {
63+
val notificationIntent = Intent(this, MainActivity::class.java).apply {
64+
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
65+
}
66+
val pendingIntent = PendingIntent.getActivity(
67+
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
68+
)
69+
70+
// Create stop action that will close both service and app
71+
val stopIntent = Intent(this, LightningNodeService::class.java).apply {
72+
action = ACTION_STOP_SERVICE_AND_APP
73+
}
74+
val stopPendingIntent = PendingIntent.getService(
75+
this, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE
76+
)
77+
78+
return NotificationCompat.Builder(this, CHANNEL_ID_NODE)
79+
.setContentTitle(getString(R.string.app_name))
80+
.setContentText(contentText)
81+
.setSmallIcon(R.drawable.ic_launcher_fg_regtest)
82+
.setContentIntent(pendingIntent)
83+
.addAction(
84+
R.drawable.ic_x,
85+
"Stop App", // TODO: Get from resources
86+
stopPendingIntent
87+
)
88+
.build()
89+
}
90+
91+
// Update the onStartCommand method
92+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
93+
Logger.debug("onStartCommand", context = TAG)
94+
when (intent?.action) {
95+
ACTION_STOP_SERVICE_AND_APP -> {
96+
Logger.debug("ACTION_STOP_SERVICE_AND_APP detected", context = TAG)
97+
// Close all activities
98+
App.currentActivity?.value?.finishAffinity()
99+
// Stop the service
100+
stopSelf()
101+
return START_NOT_STICKY
102+
}
103+
}
104+
return START_STICKY
105+
}
106+
107+
override fun onDestroy() {
108+
Logger.debug("onDestroy", context = TAG)
109+
serviceScope.launch {
110+
lightningRepo.stop().onSuccess {
111+
serviceScope.cancel()
112+
}
113+
}
114+
super.onDestroy()
115+
}
116+
117+
override fun onBind(intent: Intent?): IBinder? = null
118+
119+
companion object {
120+
private const val NOTIFICATION_ID = 1
121+
const val CHANNEL_ID_NODE = "bitkit_notification_channel_node"
122+
const val TAG = "LightningNodeService"
123+
const val ACTION_STOP_SERVICE_AND_APP = "to.bitkit.androidServices.action.STOP_SERVICE_AND_APP"
124+
}
125+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@file:Suppress("unused")
2+
3+
package to.bitkit.di
4+
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
import org.lightningdevkit.ldknode.Network
10+
import to.bitkit.env.Env
11+
12+
@Module
13+
@InstallIn(SingletonComponent::class)
14+
object EnvModule {
15+
16+
@Provides
17+
fun provideNetwork(): Network {
18+
return Env.network
19+
}
20+
}

0 commit comments

Comments
 (0)