Skip to content

Commit b252136

Browse files
committed
a third one
1 parent a56be15 commit b252136

22 files changed

+814
-50
lines changed

.idea/dictionaries/nadav.xml

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,15 @@ dependencies {
4141
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
4242
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
4343
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
44+
implementation 'androidx.preference:preference:1.1.1'
45+
46+
implementation "androidx.work:work-runtime-ktx:2.4.0"
47+
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
48+
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
49+
4450
testImplementation 'junit:junit:4.+'
4551
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
4652
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
53+
54+
implementation 'com.google.code.gson:gson:2.8.6'
4755
}

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
android:roundIcon="@mipmap/ic_launcher_round"
1010
android:supportsRtl="true"
1111
android:theme="@style/Theme.BitcoinWalletTracker">
12+
<activity
13+
android:name=".SettingsActivity"
14+
android:label="@string/title_activity_settings" />
1215
<activity
1316
android:name=".MainActivity"
1417
android:label="@string/app_name"
@@ -21,4 +24,5 @@
2124
</activity>
2225
</application>
2326

27+
<uses-permission android:name="android.permission.INTERNET"/>
2428
</manifest>

app/src/main/java/dev/bwt/NativeBwtDaemon.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package dev.bwt
22

3-
class NativeBitcoinWalletTracker {
3+
class NativeBwtDaemon {
44
init {
55
System.loadLibrary("bwt")
66
}
77

8-
external fun helloWorld(x: String): String;
8+
// Start the bwt daemon with the configured servers
9+
// Blocks until the initial indexing is completed and the servers are ready.
10+
// Returns a pointer to be used with shutdown()
911
external fun start(jsonConfig: String, callback: CallbackNotifier): Long;
12+
13+
// Shutdown thw bwt daemon
1014
external fun shutdown(shutdownPtr: Long);
1115
}
1216

app/src/main/java/dev/bwt/app/App.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dev.bwt.app
2+
3+
import android.app.Application
4+
import android.util.Log
5+
import androidx.work.Configuration
6+
7+
class App : Application(), Configuration.Provider {
8+
override fun getWorkManagerConfiguration() =
9+
Configuration.Builder()
10+
.setMinimumLoggingLevel(Log.VERBOSE)
11+
.build()
12+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
}

app/src/main/java/dev/bwt/app/FirstFragment.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import android.view.LayoutInflater
66
import android.view.View
77
import android.view.ViewGroup
88
import android.widget.Button
9+
import android.widget.TextView
10+
import androidx.activity.viewModels
11+
import androidx.fragment.app.viewModels
912
import androidx.navigation.fragment.findNavController
13+
import java.util.Observer
1014

1115
/**
1216
* A simple [Fragment] subclass as the default destination in the navigation.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package dev.bwt.app
2+
3+
import android.util.Log
4+
import androidx.lifecycle.ViewModel
5+
import androidx.lifecycle.liveData
6+
import androidx.lifecycle.viewModelScope
7+
import kotlinx.coroutines.Dispatchers
8+
9+
// https://stackoverflow.com/questions/12692103/read-logcat-programmatically-within-application
10+
11+
class LogCatViewModel : ViewModel() {
12+
fun logCatOutput() = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
13+
Runtime.getRuntime().exec("logcat -c")
14+
Runtime.getRuntime().exec("logcat")
15+
.inputStream
16+
.bufferedReader()
17+
.useLines { lines ->
18+
lines.forEach { line -> emit(line) }
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)