1
+ package dev.bwt.app
2
+
3
+ import android.app.NotificationChannel
4
+ import android.app.NotificationManager
5
+ import android.app.PendingIntent
6
+ import android.content.Context
7
+ import android.content.Intent
8
+ import android.os.Build
9
+ import android.util.Log
10
+ import androidx.annotation.RequiresApi
11
+ import androidx.core.app.NotificationCompat
12
+ import androidx.core.app.TaskStackBuilder
13
+ import androidx.work.*
14
+ import com.google.gson.Gson
15
+ import dev.bwt.daemon.BwtConfig
16
+ import dev.bwt.daemon.BwtDaemon
17
+ import dev.bwt.daemon.BwtException
18
+ import dev.bwt.daemon.ProgressNotifier
19
+ import kotlinx.coroutines.CancellationException
20
+ import kotlinx.coroutines.async
21
+ import kotlinx.coroutines.coroutineScope
22
+ import java.text.SimpleDateFormat
23
+ import java.util.*
24
+
25
+ class BwtWorker (
26
+ context : Context ,
27
+ params : WorkerParameters
28
+ ) : CoroutineWorker(context, params) {
29
+
30
+ private val notificationManager =
31
+ context.getSystemService(Context .NOTIFICATION_SERVICE ) as NotificationManager
32
+
33
+ override suspend fun doWork (): Result {
34
+ val jsonConfig = inputData.getString(" JSON_CONFIG" ) ? : return Result .failure()
35
+ val config = Gson ().fromJson(jsonConfig, BwtConfig ::class .java)
36
+ val bwt = BwtDaemon (config)
37
+
38
+ val callback = object : ProgressNotifier {
39
+ override fun onBooting () {
40
+ setProgressAsync(
41
+ workDataOf(
42
+ " TYPE" to " booting" ,
43
+ " PROGRESS" to 0 ,
44
+ )
45
+ )
46
+ Log .d(" bwt-worker" , " bwt starting up" )
47
+ }
48
+ override fun onScanProgress (progress : Float , eta : Int ) {
49
+ val progressStr = " %.1f" .format(progress)
50
+ val etaMinStr = " %.1f" .format(eta / 60 )
51
+ setProgressAsync(
52
+ workDataOf(
53
+ " TYPE" to " scan" ,
54
+ " PROGRESS" to progress,
55
+ " ETA" to eta
56
+ )
57
+ )
58
+ setForegroundAsync(createForegroundInfo(" History scanning in progress... ${progressStr} % done, eta ${etaMinStr} minute(s)" ))
59
+ Log .d(" bwt-worker" , " scan progress ${progressStr} %, eta ${etaMinStr} minute(s)" )
60
+ }
61
+
62
+ override fun onSyncProgress (progress : Float , tip : Date ) {
63
+ val progressStr = " %.1f" .format(progress)
64
+ val tipStr = fmtDate(tip)
65
+ setProgressAsync(
66
+ workDataOf(
67
+ " TYPE" to " sync" ,
68
+ " PROGRESS" to progress,
69
+ " TIP" to tip.time,
70
+ " TIP_STR" to tipStr,
71
+ )
72
+ )
73
+ setForegroundAsync(createForegroundInfo(" Node syncing in progress... ${progressStr} % done, up to ${tipStr} " ))
74
+ Log .d(" bwt-worker" , " sync progress ${progressStr} % up to ${tipStr} " )
75
+ }
76
+
77
+ override fun onReady (bwt : BwtDaemon ) {
78
+ Log .d(" bwt-worker" , " servers ready" )
79
+ setProgressAsync(
80
+ workDataOf(
81
+ " TYPE" to " ready" ,
82
+ " PROGRESS" to 1.0 ,
83
+ " ELECTRUM_ADDR" to bwt.electrumAddr,
84
+ " HTTP_ADDR" to bwt.httpAddr,
85
+ )
86
+ )
87
+ setForegroundAsync(createForegroundInfo(" Bitcoin Wallet Tracker is running." ))
88
+ }
89
+ }
90
+
91
+ return coroutineScope {
92
+ try {
93
+ setForeground(createForegroundInfo(" Starting Bitcoin Wallet Tracker..." ))
94
+ Log .d(" bwt-worker" , " starting up" )
95
+ val job = async { bwt.start(callback) }
96
+ job.await()
97
+ Result .success()
98
+ } catch (e: CancellationException ) {
99
+ Log .e(" bwt-worker" , " worker canceled" )
100
+ bwt.shutdown()
101
+ Log .e(" bwt-worker" , " shut down" )
102
+ Result .success()
103
+ } catch (e: BwtException ) {
104
+ Log .e(" bwt-worker" , " bwt error: $e " )
105
+ bwt.shutdown()
106
+ reportError(e)
107
+ Result .retry()
108
+ } catch (e: Exception ) {
109
+ Log .e(" bwt-worker" , " error: $e " )
110
+ reportError(e)
111
+ bwt.shutdown()
112
+ Result .failure(workDataOf(" ERROR" to e))
113
+ }
114
+ }
115
+ }
116
+
117
+ private suspend fun reportError (e : Exception ) {
118
+ setProgress(workDataOf(" TYPE" to " error" , " MESSAGE" to e.toString()))
119
+ }
120
+
121
+ private fun createForegroundInfo (text : String ): ForegroundInfo {
122
+ val openIntent = createNotifyOpenIntent()
123
+ val cancelIntent = WorkManager .getInstance(applicationContext)
124
+ .createCancelPendingIntent(id)
125
+
126
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .O ) {
127
+ createChannel()
128
+ }
129
+
130
+ val notification = NotificationCompat .Builder (applicationContext, CHANNEL_ID )
131
+ .setContentTitle(" Bitcoin Wallet Tracker" )
132
+ .setTicker(" Bitcoin Wallet Tracker" )
133
+ .setContentText(text)
134
+ // .setSmallIcon(R.drawable.ic_work_notification)
135
+ .setSmallIcon(android.R .drawable.sym_def_app_icon)
136
+ .setOngoing(true )
137
+ .setContentIntent(openIntent)
138
+ .addAction(android.R .drawable.ic_dialog_info, " Open" , openIntent)
139
+ .addAction(android.R .drawable.ic_delete, " Stop" , cancelIntent)
140
+ .build()
141
+
142
+ return ForegroundInfo (NOTIFICATION_ID , notification)
143
+ }
144
+
145
+ private fun createNotifyOpenIntent (): PendingIntent ? {
146
+ val notifyIntent = Intent (applicationContext, MainActivity ::class .java)
147
+ return TaskStackBuilder .create(applicationContext).run {
148
+ addNextIntentWithParentStack(notifyIntent)
149
+ getPendingIntent(0 , PendingIntent .FLAG_UPDATE_CURRENT )
150
+ }
151
+ }
152
+
153
+ @RequiresApi(Build .VERSION_CODES .O )
154
+ private fun createChannel () {
155
+ // XXX which priority level?
156
+ val channel = NotificationChannel (
157
+ CHANNEL_ID ,
158
+ " Bitcoin Wallet Tracker" ,
159
+ NotificationManager .IMPORTANCE_MIN
160
+ )
161
+ notificationManager.createNotificationChannel(channel)
162
+ }
163
+
164
+ companion object {
165
+ const val CHANNEL_ID = " BWT"
166
+ const val NOTIFICATION_ID = 1
167
+ }
168
+ }
169
+
170
+ private fun fmtDate (date : Date ): String {
171
+ val formatter = SimpleDateFormat (" yyyy-MM-dd" , Locale .getDefault())
172
+ return formatter.format(date)
173
+ }
0 commit comments