diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 0a33cb8..1552c0d 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -v0.0.2-87-gc6f1c4d-2021-06-17-22-45 +v0.0.2-99-gc864bcc-2021-06-22-00-52 \ No newline at end of file diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index 50b94d5..48f908c 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -55,8 +55,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : if (Cwtch.startCwtch(appDir, torPath) != 0.toLong()) return Result.failure() - // infinite coroutine :) + Log.i("FlwtchWorker.kt", "startCwtch success, starting coroutine AppbusEvent loop...") while(true) { + Log.i("FlwtchWorker.kt", "while(true)getAppbusEvent()") val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { val data = JSONObject(evt.Data) @@ -213,11 +214,20 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val id = "flwtch" val title = "Flwtch" val cancel = "Shut down"//todo: translate + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createForegroundNotificationChannel(id, id) + } 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) + "" + } + // This PendingIntent can be used to cancel the worker val intent = WorkManager.getInstance(applicationContext) .createCancelPendingIntent(getId()) - val notification = NotificationCompat.Builder(applicationContext, id) + val notification = NotificationCompat.Builder(applicationContext, channelId) .setContentTitle(title) .setTicker(title) .setContentText(progress) @@ -231,10 +241,18 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : return ForegroundInfo(101, notification) } + @RequiresApi(Build.VERSION_CODES.O) + private fun createForegroundNotificationChannel(channelId: String, channelName: String): String{ + val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE) + chan.lightColor = Color.MAGENTA + chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE + notificationManager.createNotificationChannel(chan) + return channelId + } + @RequiresApi(Build.VERSION_CODES.O) private fun createMessageNotificationChannel(channelId: String, channelName: String): String{ - val chan = NotificationChannel(channelId, - channelName, NotificationManager.IMPORTANCE_HIGH) + val chan = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH) chan.lightColor = Color.MAGENTA chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE notificationManager.createNotificationChannel(chan) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt index e9a6d11..da889d7 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -7,40 +7,23 @@ import android.content.Intent import android.content.IntentFilter import androidx.annotation.NonNull import android.content.pm.PackageManager -import android.os.Build -import android.os.Bundle -import android.os.Looper import android.util.Log import android.view.Window -import android.widget.Toast -import androidx.annotation.RequiresApi import androidx.lifecycle.Observer import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch - import io.flutter.embedding.android.SplashScreen import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import cwtch.Cwtch -import io.flutter.plugin.common.EventChannel -import kotlin.concurrent.thread - import org.json.JSONObject -import java.io.File -import java.time.Duration import java.util.concurrent.TimeUnit class MainActivity: FlutterActivity() { - override fun provideSplashScreen(): SplashScreen? = SplashView() // Channel to get app info @@ -56,6 +39,10 @@ class MainActivity: FlutterActivity() { // Channel to trigger contactview when an external notification is clicked private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler" + // WorkManager tag applied to all Start() infinite coroutines + val WORKER_TAG = "cwtchEventBusWorker" + + private var myReceiver: MyBroadcastReceiver? = null private var methodChan: MethodChannel? = null override fun onNewIntent(intent: Intent) { @@ -106,42 +93,28 @@ class MainActivity: FlutterActivity() { // in case the ForegroundService is still running. in both cases, however, we *do* want to re-register // the eventbus listener. if (call.method == "Start") { - val workerTag = "cwtchEventBusWorker" val uniqueTag = argmap["torPath"] ?: "nullEventBus" // note: because the ForegroundService is specified as UniquePeriodicWork, it can't actually get // accidentally duplicated. however, we still need to manually check if it's running or not, so // that we can divert this method call to ReconnectCwtchForeground instead if so. - val works = WorkManager.getInstance(this).getWorkInfosByTag(workerTag).get() - var alreadyRunning = false + val works = WorkManager.getInstance(this).getWorkInfosByTag(WORKER_TAG).get() for (workInfo in works) { - if (workInfo.tags.contains(uniqueTag)) { - if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) { - alreadyRunning = true - } - } else { + Log.i("handleCwtch:WorkManager", "$workInfo") + if (!workInfo.tags.contains(uniqueTag)) { + Log.i("handleCwtch:WorkManager", "canceling ${workInfo.id} bc tags don't include $uniqueTag") WorkManager.getInstance(this).cancelWorkById(workInfo.id) } } + WorkManager.getInstance(this).pruneWork() - // 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(15, TimeUnit.MINUTES).setInputData(data).addTag(workerTag).addTag(uniqueTag).build() - WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.KEEP, workRequest) - return - } + 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(15, TimeUnit.MINUTES).setInputData(data).addTag(WORKER_TAG).addTag(uniqueTag).build() + WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.REPLACE, workRequest) + return } // ...otherwise fallthru to a normal ffi method call (and return the result using the result callback) @@ -158,6 +131,35 @@ class MainActivity: FlutterActivity() { ) } + // using onresume/onstop for broadcastreceiver because of extended discussion on https://stackoverflow.com/questions/7439041/how-to-unregister-broadcastreceiver + override fun onResume() { + super.onResume() + Log.i("MainActivity.kt", "onResume") + if (myReceiver == null) { + Log.i("MainActivity.kt", "onResume registering localbroadcastreceiver") + val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS) + val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS") + myReceiver = MyBroadcastReceiver(mc) + LocalBroadcastManager.getInstance(applicationContext).registerReceiver(myReceiver!!, filter) + } + } + + override fun onStop() { + super.onStop() + Log.i("MainActivity.kt", "onStop") + if (myReceiver != null) { + LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(myReceiver!!); + myReceiver = null; + } + } + + override fun onDestroy() { + super.onDestroy() + Log.i("MainActivity.kt", "onDestroy") + WorkManager.getInstance(this).cancelAllWorkByTag(WORKER_TAG) + WorkManager.getInstance(this).pruneWork() + } + // source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095 // for reference: // diff --git a/lib/main.dart b/lib/main.dart index 7f61f30..aa04a97 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ var globalAppState = AppState(); void main() { print("main()"); LicenseRegistry.addLicense(() => licenses()); + WidgetsFlutterBinding.ensureInitialized(); print("runApp()"); runApp(Flwtch()); } @@ -55,11 +56,14 @@ class FlwtchState extends State { @override initState() { + print("initState: running..."); super.initState(); + print("initState: registering notification, shutdown handlers..."); profs = ProfileListState(); notificationClickChannel.setMethodCallHandler(_externalNotificationClicked); shutdownMethodChannel.setMethodCallHandler(shutdown); + print("initState: creating cwtchnotifier, ffi"); if (Platform.isAndroid) { var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState); cwtch = CwtchGomobile(cwtchNotifier); @@ -70,7 +74,9 @@ class FlwtchState extends State { var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState); cwtch = CwtchFfi(cwtchNotifier); } + print("initState: invoking cwtch.Start()"); cwtch.Start(); + print("initState: done!"); } ChangeNotifierProvider getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);