android service and notification support take one. ACTION
This commit is contained in:
parent
89507a8aa6
commit
9c69275fe6
|
@ -114,6 +114,7 @@ dependencies {
|
||||||
|
|
||||||
// end of workmanager deps
|
// end of workmanager deps
|
||||||
|
|
||||||
// ipc
|
// needed to prevent a ListenableFuture dependency conflict/bug
|
||||||
implementation "io.reactivex:rxkotlin:1.x.y"
|
// see https://github.com/google/ExoPlayer/issues/7905#issuecomment-692637059
|
||||||
|
implementation 'com.google.guava:guava:any'
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,4 +45,7 @@
|
||||||
|
|
||||||
<!--Needed to run in background (lol)-->
|
<!--Needed to run in background (lol)-->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
|
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
|
||||||
|
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
package im.cwtch.flwtch
|
package im.cwtch.flwtch
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.*
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Context.ACTIVITY_SERVICE
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import cwtch.Cwtch
|
import cwtch.Cwtch
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.FlutterInjector
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +25,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as
|
||||||
NotificationManager
|
NotificationManager
|
||||||
|
|
||||||
|
private var notificationID: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
private var notificationIDnext: Int = 1;
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val method = inputData.getString(KEY_METHOD)
|
val method = inputData.getString(KEY_METHOD)
|
||||||
?: return Result.failure()
|
?: return Result.failure()
|
||||||
|
@ -36,38 +37,96 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
// Mark the Worker as important
|
// Mark the Worker as important
|
||||||
val progress = "Trying to do a Flwtch"
|
val progress = "Trying to do a Flwtch"
|
||||||
setForeground(createForegroundInfo(progress))
|
setForeground(createForegroundInfo(progress))
|
||||||
handleCwtch(method, args)
|
return handleCwtch(method, args)
|
||||||
return Result.success()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleCwtch(method: String, args: String) {
|
private fun getNotificationID(profile: String, contact: String): Int {
|
||||||
var a = JSONObject(args);
|
val k = "$profile $contact"
|
||||||
|
if (!notificationID.containsKey(k)) {
|
||||||
|
notificationID[k] = notificationIDnext++
|
||||||
|
}
|
||||||
|
return notificationID[k] ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleCwtch(method: String, args: String): Result {
|
||||||
|
val a = JSONObject(args);
|
||||||
when (method) {
|
when (method) {
|
||||||
"Start" -> {
|
"Start" -> {
|
||||||
Log.i("FlwtchWorker.kt", "handleAppInfo Start")
|
Log.i("FlwtchWorker.kt", "handleAppInfo Start")
|
||||||
val appDir = (a.get("appDir") as? String) ?: "";
|
val appDir = (a.get("appDir") as? String) ?: ""
|
||||||
val torPath = (a.get("torPath") as? String) ?: "tor";
|
val torPath = (a.get("torPath") as? String) ?: "tor"
|
||||||
Log.i("FlwtchWorker.kt", " appDir: '" + appDir + "' torPath: '" + torPath + "'")
|
Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'")
|
||||||
|
|
||||||
Cwtch.startCwtch(appDir, torPath)
|
if (Cwtch.startCwtch(appDir, torPath) != 0.toByte()) return Result.failure()
|
||||||
|
|
||||||
// infinite coroutine :)
|
// infinite coroutine :)
|
||||||
while(true) {
|
while(true) {
|
||||||
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
|
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
|
||||||
Log.i("FlwtchWorker.kt", "got appbusEvent: " + evt)
|
//Log.i("FlwtchWorker.kt", "got appbusEvent: " + evt)
|
||||||
if (isStopped) {
|
if (isStopped) {
|
||||||
Log.i("FlwtchWorker.kt", "COROUTINEWORKER DOT ISSTOPPED TRUE OH MY")
|
Log.i("FlwtchWorker.kt", "COROUTINEWORKER DOT ISSTOPPED TRUE OH MY")
|
||||||
}
|
}
|
||||||
//todo: this elides evt.EventID which may be needed at some point?
|
|
||||||
val data = Data.Builder()
|
if (evt.EventType == "NewMessageFromPeer") {
|
||||||
.putString("EventType", evt.EventType)
|
val data = JSONObject(evt.Data)
|
||||||
.putString("Data", evt.Data)
|
|
||||||
.putString("EventID", evt.EventID)
|
// val appProcesses: List<ActivityManager.RunningAppProcessInfo> = (applicationContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses
|
||||||
|
// for (appProcess in appProcesses) {
|
||||||
|
// if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
|
||||||
|
// Log.i("Foreground App", appProcess.processName)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
val channelId =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer"))
|
||||||
|
} else {
|
||||||
|
// If earlier version channel ID is not used
|
||||||
|
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
val loader = FlutterInjector.instance().flutterLoader()
|
||||||
|
val key = loader.getLookupKeyForAsset("assets/"+data.getString("Picture"))//"assets/profiles/001-centaur.png")
|
||||||
|
val fh = applicationContext.assets.open(key)
|
||||||
|
|
||||||
|
val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent ->
|
||||||
|
intent.setAction(Intent.ACTION_RUN)//("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
|
||||||
|
//intent.setClassName("im.cwtch.flwtch", "MainActivity")
|
||||||
|
intent.putExtra("EventType", "NotificationClicked")
|
||||||
|
intent.putExtra("ProfileOnion", data.getString("ProfileOnion"))
|
||||||
|
intent.putExtra("RemotePeer", data.getString("RemotePeer"))
|
||||||
|
}
|
||||||
|
|
||||||
|
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
|
||||||
|
.setContentTitle(data.getString("Nick"))
|
||||||
|
.setContentText("New message")
|
||||||
|
.setLargeIcon(BitmapFactory.decodeStream(fh))
|
||||||
|
.setSmallIcon(R.mipmap.knott)
|
||||||
|
.setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
|
.setAutoCancel(true)
|
||||||
.build()
|
.build()
|
||||||
setProgress(data)
|
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), data.getString("RemotePeer")), newNotification)
|
||||||
Thread.sleep(200)
|
}
|
||||||
|
//todo: this elides evt.EventID which may be needed at some point?
|
||||||
|
// val data = Data.Builder()
|
||||||
|
// .putString("EventType", evt.EventType)
|
||||||
|
// .putString("Data", evt.Data)
|
||||||
|
// .putString("EventID", evt.EventID)
|
||||||
|
// .build()
|
||||||
|
//setProgress(data)//progress can only hold a single undelivered value so it's possible for observers to miss rapidfire updates
|
||||||
|
//Thread.sleep(200)//this is a kludge to make it mostly-work until a proper channel is implemented
|
||||||
|
Intent().also { intent ->
|
||||||
|
intent.setAction("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
|
||||||
|
intent.putExtra("EventType", evt.EventType)
|
||||||
|
intent.putExtra("Data", evt.Data)
|
||||||
|
intent.putExtra("EventID", evt.EventID)
|
||||||
|
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"ReconnectCwtchForeground" -> {
|
||||||
|
Cwtch.reconnectCwtchForeground()
|
||||||
|
}
|
||||||
"SelectProfile" -> {
|
"SelectProfile" -> {
|
||||||
val onion = (a.get("profile") as? String) ?: "";
|
val onion = (a.get("profile") as? String) ?: "";
|
||||||
Cwtch.selectProfile(onion)
|
Cwtch.selectProfile(onion)
|
||||||
|
@ -87,7 +146,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
"NumMessages" -> {
|
"NumMessages" -> {
|
||||||
val profile = (a.get("profile") as? String) ?: "";
|
val profile = (a.get("profile") as? String) ?: "";
|
||||||
val handle = (a.get("contact") as? String) ?: "";
|
val handle = (a.get("contact") as? String) ?: "";
|
||||||
Result.success(Data.Builder().putLong("result", Cwtch.numMessages(profile, handle)).build())
|
return Result.success(Data.Builder().putLong("result", Cwtch.numMessages(profile, handle)).build())
|
||||||
}
|
}
|
||||||
"GetMessage" -> {
|
"GetMessage" -> {
|
||||||
//Log.i("MainActivivity.kt", (a.get("index")));
|
//Log.i("MainActivivity.kt", (a.get("index")));
|
||||||
|
@ -100,14 +159,14 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
val handle = (a.get("contact") as? String) ?: "";
|
val handle = (a.get("contact") as? String) ?: "";
|
||||||
val indexI = a.getInt("index") ?: 0;
|
val indexI = a.getInt("index") ?: 0;
|
||||||
Log.i("FlwtchWorker.kt", "indexI = " + indexI)
|
Log.i("FlwtchWorker.kt", "indexI = " + indexI)
|
||||||
Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
|
return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build())
|
||||||
}
|
}
|
||||||
"GetMessages" -> {
|
"GetMessages" -> {
|
||||||
val profile = (a.get("profile") as? String) ?: "";
|
val profile = (a.get("profile") as? String) ?: "";
|
||||||
val handle = (a.get("contact") as? String) ?: "";
|
val handle = (a.get("contact") as? String) ?: "";
|
||||||
val start = (a.get("start") as? Long) ?: 0;
|
val start = (a.get("start") as? Long) ?: 0;
|
||||||
val end = (a.get("end") as? Long) ?: 0;
|
val end = (a.get("end") as? Long) ?: 0;
|
||||||
Result.success(Data.Builder().putString("result", Cwtch.getMessages(profile, handle, start, end)).build())
|
return Result.success(Data.Builder().putString("result", Cwtch.getMessages(profile, handle, start, end)).build())
|
||||||
}
|
}
|
||||||
"AcceptContact" -> {
|
"AcceptContact" -> {
|
||||||
val profile = (a.get("ProfileOnion") as? String) ?: "";
|
val profile = (a.get("ProfileOnion") as? String) ?: "";
|
||||||
|
@ -177,26 +236,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
val groupHandle = (a.get("groupHandle") as? String) ?: "";
|
val groupHandle = (a.get("groupHandle") as? String) ?: "";
|
||||||
Cwtch.rejectInvite(profile, groupHandle);
|
Cwtch.rejectInvite(profile, groupHandle);
|
||||||
}
|
}
|
||||||
else -> Result.failure()
|
else -> return Result.failure()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun launchCwtch(appDir: String, torPath: String) {
|
|
||||||
Cwtch.startCwtch(appDir, torPath)
|
|
||||||
// seperate coroutine to poll event bus and send to dart
|
|
||||||
//Log.i("FlwtchWorker.kt", "got event chan: " + eventbus_chan + " launching corouting...")
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
while(true) {
|
|
||||||
val evt = AppbusEvent(Cwtch.getAppBusEvent())
|
|
||||||
Log.i("FlwtchWorker.kt", "got appbusEvent: " + evt)
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
//todo: this elides evt.EventID which may be needed at some point?
|
|
||||||
val flutterEngine = FlutterEngine(applicationContext)
|
|
||||||
val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
|
||||||
eventbus_chan.invokeMethod(evt.EventType, evt.Data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates an instance of ForegroundInfo which can be used to update the
|
// Creates an instance of ForegroundInfo which can be used to update the
|
||||||
|
@ -243,6 +285,17 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
return channelId
|
return channelId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createMessageNotificationChannel(channelId: String, channelName: String): String{
|
||||||
|
val chan = NotificationChannel(channelId,
|
||||||
|
channelName, NotificationManager.IMPORTANCE_HIGH)
|
||||||
|
chan.lightColor = Color.MAGENTA
|
||||||
|
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
|
||||||
|
notificationManager.createNotificationChannel(chan)
|
||||||
|
return channelId
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KEY_METHOD = "KEY_METHOD"
|
const val KEY_METHOD = "KEY_METHOD"
|
||||||
const val KEY_ARGS = "KEY_ARGS"
|
const val KEY_ARGS = "KEY_ARGS"
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
package im.cwtch.flwtch
|
package im.cwtch.flwtch
|
||||||
|
|
||||||
import SplashView
|
import SplashView
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -27,6 +35,8 @@ import kotlin.concurrent.thread
|
||||||
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
|
|
||||||
|
@ -42,11 +52,32 @@ class MainActivity: FlutterActivity() {
|
||||||
// Channel to send eventbus events on
|
// Channel to send eventbus events on
|
||||||
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus"
|
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus"
|
||||||
|
|
||||||
|
// Channel to trigger contactview when an external notification is clicked
|
||||||
|
private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler"
|
||||||
|
|
||||||
|
private var methodChan: MethodChannel? = null
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
if (methodChan == null || intent.extras == null) return
|
||||||
|
if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("RemotePeer")) {
|
||||||
|
Log.i("onNewIntent", "got intent with no onions")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val profile = intent.extras!!.getString("ProfileOnion")
|
||||||
|
val handle = intent.extras!!.getString("RemotePeer")
|
||||||
|
val mappo = mapOf("ProfileOnion" to profile, "RemotePeer" to handle)
|
||||||
|
Log.i("MainActivity.kt", "onNewIntent($profile, $handle)")
|
||||||
|
val j = JSONObject(mappo)
|
||||||
|
methodChan!!.invokeMethod("NotificationClicked", j.toString())
|
||||||
|
}
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
// Note: this methods are invoked on the main thread.
|
// Note: this methods are invoked on the main thread.
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) }
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) }
|
||||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) }
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) }
|
||||||
|
methodChan = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
|
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||||
|
@ -61,49 +92,72 @@ class MainActivity: FlutterActivity() {
|
||||||
val ainfo = this.applicationContext.packageManager.getApplicationInfo(
|
val ainfo = this.applicationContext.packageManager.getApplicationInfo(
|
||||||
"im.cwtch.flwtch", // Must be app name
|
"im.cwtch.flwtch", // Must be app name
|
||||||
PackageManager.GET_SHARED_LIBRARY_FILES)
|
PackageManager.GET_SHARED_LIBRARY_FILES)
|
||||||
|
|
||||||
return ainfo.nativeLibraryDir
|
return ainfo.nativeLibraryDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// receives messages from the ForegroundService (which provides, ironically enough, the backend)
|
||||||
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
|
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||||
|
var method = call.method
|
||||||
val argmap: Map<String, String> = call.arguments as Map<String, String>
|
val argmap: Map<String, String> = call.arguments as Map<String, String>
|
||||||
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, call.method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
|
|
||||||
var tag = ""
|
|
||||||
if (call.method == "Start") {
|
|
||||||
tag = "cwtchEventBus"
|
|
||||||
}
|
|
||||||
val workRequest: WorkRequest = OneTimeWorkRequestBuilder<FlwtchWorker>().setInputData(data).addTag(tag).build()
|
|
||||||
WorkManager.getInstance(this).enqueue(workRequest)
|
|
||||||
if (call.method == "Start") {
|
|
||||||
val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
|
||||||
WorkManager.getInstance(applicationContext)
|
|
||||||
// requestId is the WorkRequest id
|
|
||||||
.getWorkInfosByTagLiveData("cwtchEventBus")
|
|
||||||
.observe(this, Observer<List<WorkInfo>> { listOfWorkInfo ->
|
|
||||||
if (listOfWorkInfo.isNullOrEmpty()) {
|
|
||||||
return@Observer
|
|
||||||
}
|
|
||||||
|
|
||||||
for (workInfo in listOfWorkInfo) {
|
// the frontend calls Start every time it fires up, but we don't want to *actually* call Cwtch.Start()
|
||||||
if (workInfo != null) {
|
// in case the ForegroundService is still running. in both cases, however, we *do* want to re-register
|
||||||
val progress = workInfo.progress
|
// the eventbus listener.
|
||||||
val eventType = progress.getString("EventType") ?: ""
|
if (call.method == "Start") {
|
||||||
val eventData = progress.getString("Data")
|
val workerTag = "cwtchEventBusWorker"
|
||||||
val output = progress.keyValueMap.toString()
|
val uniqueTag = argmap["torPath"] ?: "nullEventBus"
|
||||||
try {
|
|
||||||
if (eventType != "") {
|
// note: because the ForegroundService is specified as UniquePeriodicWork, it can't actually get
|
||||||
Log.i("MainActivity.kt", "got event $output $eventType $eventData")
|
// accidentally duplicated. however, we still need to manually check if it's running or not, so
|
||||||
eventbus_chan.invokeMethod(eventType, eventData)
|
// that we can divert this method call to ReconnectCwtchForeground instead if so.
|
||||||
}
|
val works = WorkManager.getInstance(this).getWorkInfosByTag(workerTag).get()
|
||||||
} catch (e: Exception) {
|
var alreadyRunning = false
|
||||||
Log.i("MainActivity.kt", "event bus exception")
|
for (workInfo in works) {
|
||||||
}
|
if (workInfo.tags.contains(uniqueTag)) {
|
||||||
} else {
|
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||||
Log.i("MainActivity.kt", "got null workInfo")
|
alreadyRunning = true
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
})
|
WorkManager.getInstance(this).cancelWorkById(workInfo.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// register our eventbus listener. note that we observe any/all work according to its tag, which
|
||||||
|
// results in an implicit "reconnection" to old service threads even after frontend restarts
|
||||||
|
val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS)
|
||||||
|
val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
|
||||||
|
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(MyBroadcastReceiver(mc), filter)
|
||||||
|
|
||||||
|
if (alreadyRunning) {
|
||||||
|
Log.i("MainActivity.kt", "diverting Start -> Reconnect")
|
||||||
|
method = "ReconnectCwtchForeground"
|
||||||
|
} else {
|
||||||
|
Log.i("MainActivity.kt", "Start() launching foregroundservice")
|
||||||
|
// this is where the eventbus ForegroundService gets launched. WorkManager should keep it alive after this
|
||||||
|
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, call.method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
|
||||||
|
// 15 minutes is the shortest interval you can request
|
||||||
|
val workRequest = PeriodicWorkRequestBuilder<FlwtchWorker>(15, TimeUnit.MINUTES).setInputData(data).addTag(workerTag).addTag(uniqueTag).build()
|
||||||
|
val workResult = WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.KEEP, workRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ...otherwise fallthru to a normal ffi method call (and return the result using the result callback)
|
||||||
|
val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build()
|
||||||
|
val workRequest = OneTimeWorkRequestBuilder<FlwtchWorker>().setInputData(data).build()
|
||||||
|
val workResult = WorkManager.getInstance(this).enqueue(workRequest)
|
||||||
|
WorkManager.getInstance(applicationContext).getWorkInfoByIdLiveData(workRequest.id).observe(
|
||||||
|
this, Observer { workInfo ->
|
||||||
|
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
|
||||||
|
val res = workInfo.outputData.keyValueMap.toString()
|
||||||
|
//Log.i("MainActivity.kt", "method $method returned SUCCESS($res)")
|
||||||
|
result.success(workInfo.outputData.getString("result"))
|
||||||
|
} else {
|
||||||
|
val idk = workInfo.state.toString()
|
||||||
|
//Log.i("MainActivity.kt", "method $method returned $idk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
|
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
|
||||||
|
@ -125,4 +179,23 @@ class MainActivity: FlutterActivity() {
|
||||||
val EventID = this.optString("EventID")
|
val EventID = this.optString("EventID")
|
||||||
val Data = this.optString("Data")
|
val Data = this.optString("Data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() {
|
||||||
|
val eventBus: MethodChannel = mc
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
// StringBuilder().apply {
|
||||||
|
// append("Action: ${intent.action}\n")
|
||||||
|
// append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n")
|
||||||
|
// toString().also { log ->
|
||||||
|
// Log.d("MyBroadcastReceiver", log)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
val evtType = intent.getStringExtra("EventType") ?: ""
|
||||||
|
val evtData = intent.getStringExtra("Data") ?: ""
|
||||||
|
//val evtID = intent.getStringExtra("EventID") ?: ""//todo?
|
||||||
|
eventBus.invokeMethod(evtType, evtData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
abstract class Cwtch {
|
abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<void> Start();
|
Future<dynamic> Start();
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> ReconnectCwtchForeground();
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void SelectProfile(String onion);
|
void SelectProfile(String onion);
|
||||||
|
|
|
@ -15,8 +15,8 @@ import '../config.dart';
|
||||||
/// Cwtch API ///
|
/// Cwtch API ///
|
||||||
/////////////////////
|
/////////////////////
|
||||||
|
|
||||||
typedef start_cwtch_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
typedef start_cwtch_function = Int8 Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||||
typedef StartCwtchFn = void Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
|
typedef StartCwtchFn = int Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
|
||||||
|
|
||||||
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||||
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||||
|
@ -76,7 +76,7 @@ class CwtchFfi implements Cwtch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<void> Start() async {
|
Future<dynamic> Start() async {
|
||||||
String home = "";
|
String home = "";
|
||||||
String bundledTor = "";
|
String bundledTor = "";
|
||||||
Map<String, String> envVars = Platform.environment;
|
Map<String, String> envVars = Platform.environment;
|
||||||
|
@ -109,6 +109,14 @@ class CwtchFfi implements Cwtch {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> ReconnectCwtchForeground() async {
|
||||||
|
var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final ReconnectCwtchForeground = reconnectCwtch.asFunction<void Function()>();
|
||||||
|
ReconnectCwtchForeground();
|
||||||
|
}
|
||||||
|
|
||||||
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
// Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|
|
@ -43,7 +43,7 @@ class CwtchGomobile implements Cwtch {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<void> Start() async {
|
Future<dynamic> Start() async {
|
||||||
print("gomobile.dart: Start()...");
|
print("gomobile.dart: Start()...");
|
||||||
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch");
|
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch");
|
||||||
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
||||||
|
@ -51,7 +51,13 @@ class CwtchGomobile implements Cwtch {
|
||||||
}
|
}
|
||||||
String torPath = path.join(await androidLibraryDir, "libtor.so");
|
String torPath = path.join(await androidLibraryDir, "libtor.so");
|
||||||
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
|
print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)...");
|
||||||
cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
|
return cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<void> ReconnectCwtchForeground() async {
|
||||||
|
cwtchPlatform.invokeMethod("ReconnectCwtchForeground", {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier
|
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/notification_manager.dart';
|
import 'package:cwtch/notification_manager.dart';
|
||||||
|
import 'package:cwtch/views/messageview.dart';
|
||||||
import 'package:cwtch/widgets/rightshiftfixer.dart';
|
import 'package:cwtch/widgets/rightshiftfixer.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cwtch/cwtch/ffi.dart';
|
import 'package:cwtch/cwtch/ffi.dart';
|
||||||
|
@ -8,6 +11,7 @@ import 'package:cwtch/errorHandler.dart';
|
||||||
import 'package:cwtch/settings.dart';
|
import 'package:cwtch/settings.dart';
|
||||||
import 'package:cwtch/torstatus.dart';
|
import 'package:cwtch/torstatus.dart';
|
||||||
import 'package:cwtch/views/triplecolview.dart';
|
import 'package:cwtch/views/triplecolview.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'cwtch/cwtch.dart';
|
import 'cwtch/cwtch.dart';
|
||||||
import 'cwtch/cwtchNotifier.dart';
|
import 'cwtch/cwtchNotifier.dart';
|
||||||
|
@ -44,6 +48,8 @@ class FlwtchState extends State<Flwtch> {
|
||||||
var columns = [1]; // default or 'single column' mode
|
var columns = [1]; // default or 'single column' mode
|
||||||
//var columns = [1, 1, 2];
|
//var columns = [1, 1, 2];
|
||||||
late ProfileListState profs;
|
late ProfileListState profs;
|
||||||
|
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
|
||||||
|
final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
|
@ -51,6 +57,7 @@ class FlwtchState extends State<Flwtch> {
|
||||||
cwtchInit = false;
|
cwtchInit = false;
|
||||||
|
|
||||||
profs = ProfileListState();
|
profs = ProfileListState();
|
||||||
|
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
|
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager());
|
||||||
|
@ -63,10 +70,9 @@ class FlwtchState extends State<Flwtch> {
|
||||||
cwtch = CwtchFfi(cwtchNotifier);
|
cwtch = CwtchFfi(cwtchNotifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
cwtch.Start().then((val) {
|
cwtch.Start();
|
||||||
setState(() {
|
setState(() {
|
||||||
cwtchInit = true;
|
cwtchInit = true;
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,13 +98,12 @@ class FlwtchState extends State<Flwtch> {
|
||||||
return Consumer<Settings>(
|
return Consumer<Settings>(
|
||||||
builder: (context, settings, child) => MaterialApp(
|
builder: (context, settings, child) => MaterialApp(
|
||||||
key: Key('app'),
|
key: Key('app'),
|
||||||
|
navigatorKey: navKey,
|
||||||
locale: settings.locale,
|
locale: settings.locale,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
title: 'Cwtch',
|
title: 'Cwtch',
|
||||||
theme: mkThemeData(settings),
|
theme: mkThemeData(settings),
|
||||||
// from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(),
|
|
||||||
// from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(),
|
|
||||||
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
|
home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -106,6 +111,26 @@ class FlwtchState extends State<Flwtch> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _externalNotificationClicked(MethodCall call) async {
|
||||||
|
var args = jsonDecode(call.arguments);
|
||||||
|
var profile = profs.getProfile(args["ProfileOnion"])!;
|
||||||
|
var contact = profile.contactList.getContact(args["RemotePeer"])!;
|
||||||
|
contact.unreadMessages = 0;
|
||||||
|
navKey.currentState?.push(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (BuildContext builderContext) {
|
||||||
|
return MultiProvider(
|
||||||
|
providers: [
|
||||||
|
ChangeNotifierProvider.value(value: profile),
|
||||||
|
ChangeNotifierProvider.value(value: contact),
|
||||||
|
],
|
||||||
|
builder: (context, child) => MessageView(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
cwtch.dispose();
|
cwtch.dispose();
|
||||||
|
|
|
@ -426,6 +426,7 @@ class MessageState extends ChangeNotifier {
|
||||||
void tryLoad(BuildContext context) {
|
void tryLoad(BuildContext context) {
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
||||||
try {
|
try {
|
||||||
|
print("debug messageJson $jsonMessage");
|
||||||
dynamic messageWrapper = jsonDecode(jsonMessage);
|
dynamic messageWrapper = jsonDecode(jsonMessage);
|
||||||
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
||||||
this._senderOnion = profileOnion;
|
this._senderOnion = profileOnion;
|
||||||
|
|
Loading…
Reference in New Issue