cwtch-ui/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt

662 lines
32 KiB
Kotlin
Raw Permalink Normal View History

2021-06-24 23:10:45 +00:00
package im.cwtch.flwtch
import SplashView
import android.annotation.TargetApi
2021-06-24 23:10:45 +00:00
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.net.Uri
import android.os.PowerManager
import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
2021-06-24 23:10:45 +00:00
import android.util.Log
import android.view.Window
import android.view.WindowManager
import androidx.annotation.NonNull
2021-06-24 23:10:45 +00:00
import androidx.lifecycle.Observer
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.*
import cwtch.Cwtch
2021-06-24 23:10:45 +00:00
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.android.SplashScreen
2021-06-24 23:10:45 +00:00
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.ErrorLogResult
2021-06-24 23:10:45 +00:00
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
2021-06-24 23:10:45 +00:00
import io.flutter.plugin.common.MethodChannel.Result
import org.json.JSONObject
2021-12-15 01:13:13 +00:00
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.*
2021-06-24 23:10:45 +00:00
class MainActivity: FlutterActivity() {
override fun provideSplashScreen(): SplashScreen? = SplashView()
2021-06-24 23:10:45 +00:00
// Channel to get app info
private val CHANNEL_APP_INFO = "test.flutter.dev/applicationInfo"
private val CALL_APP_INFO = "getNativeLibDir"
private val CALL_SERVICE_INFO = "getForegroundServiceInfo"
private val ANDROID_SETTINGS_CHANNEL_NAME = "androidSettings"
private val ANDROID_SETTINGS_CHANGE_NAME= "androidSettingsChanged"
private var andoidSettingsChangeChannel: MethodChannel? = null
private val CALL_ASK_BATTERY_EXEMPTION = "requestBatteryExemption"
private val CALL_IS_BATTERY_EXEMPT = "isBatteryExempt"
2021-06-24 23:10:45 +00:00
// Channel to get cwtch api calls on
private val CHANNEL_CWTCH = "cwtch"
// Channel to send eventbus events on
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus"
2021-06-25 00:59:54 +00:00
// Channels to trigger actions when an external notification is clicked
2021-06-24 23:10:45 +00:00
private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler"
2021-06-25 00:59:54 +00:00
private val CHANNEL_SHUTDOWN_CLICK = "im.cwtch.flwtch/shutdownClickHandler"
2021-06-24 23:10:45 +00:00
private val TAG: String = "MainActivity.kt"
2021-06-24 23:10:45 +00:00
// WorkManager tag applied to all Start() infinite coroutines
val WORKER_TAG = "cwtchEventBusWorker"
private var myReceiver: MyBroadcastReceiver? = null
2021-06-25 00:59:54 +00:00
private var notificationClickChannel: MethodChannel? = null
private var shutdownClickChannel: MethodChannel? = null
2021-06-24 23:10:45 +00:00
2021-09-27 19:53:21 +00:00
// "Download to..." prompt extra arguments
2021-10-01 19:38:06 +00:00
private val FILEPICKER_REQUEST_CODE = 234
2021-12-14 23:50:08 +00:00
private val PREVIEW_EXPORT_REQUEST_CODE = 235
2022-03-10 21:29:28 +00:00
private val PROFILE_EXPORT_REQUEST_CODE = 236
private val REQUEST_DOZE_WHITELISTING_CODE:Int = 9
2021-09-27 19:53:21 +00:00
private var dlToProfile = ""
private var dlManifestPath = ""
2022-12-12 23:10:07 +00:00
private var dlToHandle = 0
2021-09-27 19:53:21 +00:00
private var dlToFileKey = ""
2021-12-14 23:50:08 +00:00
private var exportFromPath = ""
2021-09-27 19:53:21 +00:00
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
// Todo: when we support SDK 31
// hideOverlay()
}
/*
@TargetApi(31)
fun hideOverlay() {
window.setHideOverlayWindows(true);
}
*/
// handles clicks received from outside the app (ie, notifications)
2021-06-24 23:10:45 +00:00
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
2021-06-25 00:59:54 +00:00
if (notificationClickChannel == null || intent.extras == null) return
if (intent.extras!!.getString("EventType") == "NotificationClicked") {
if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("Handle")) {
2021-06-25 00:59:54 +00:00
Log.i("onNewIntent", "got notification clicked intent with no onions")
return
}
val profile = intent.extras!!.getString("ProfileOnion")
val handle = intent.extras!!.getString("Handle")
val mappo = mapOf("ProfileOnion" to profile, "Handle" to handle)
2021-06-25 00:59:54 +00:00
val j = JSONObject(mappo)
notificationClickChannel!!.invokeMethod("NotificationClicked", j.toString())
} else if (intent.extras!!.getString("EventType") == "ShutdownClicked") {
shutdownClickChannel!!.invokeMethod("ShutdownClicked", "")
} else {
print("warning: received intent with unknown method; ignoring")
2021-06-24 23:10:45 +00:00
}
}
// handles return values from the system file picker
2021-09-27 19:53:21 +00:00
override fun onActivityResult(requestCode: Int, result: Int, intent: Intent?) {
2021-10-01 19:38:06 +00:00
super.onActivityResult(requestCode, result, intent);
// has null intent and data
if (requestCode == REQUEST_DOZE_WHITELISTING_CODE) {
// 0 == "battery optimized" (still)
// -1 == "no battery optimization" (exempt!)
andoidSettingsChangeChannel!!.invokeMethod("powerExemptionChange", result == -1)
return;
}
2021-09-27 19:53:21 +00:00
if (intent == null || intent!!.getData() == null) {
Log.i(TAG, "user canceled activity");
2021-09-27 19:53:21 +00:00
return;
}
2021-10-01 19:38:06 +00:00
if (requestCode == FILEPICKER_REQUEST_CODE) {
val filePath = intent!!.getData().toString();
2022-12-12 23:10:07 +00:00
Log.d("MainActivity:FILEPICKER_REQUEST_CODE", "DownloadableFileCreated");
2021-10-01 19:38:06 +00:00
handleCwtch(MethodCall("DownloadFile", mapOf(
"ProfileOnion" to this.dlToProfile,
2022-12-12 23:10:07 +00:00
"conversation" to this.dlToHandle.toInt(),
2021-10-01 19:38:06 +00:00
"filepath" to filePath,
"manifestpath" to this.dlManifestPath,
2021-10-01 19:38:06 +00:00
"filekey" to this.dlToFileKey
)), ErrorLogResult(""));//placeholder; this Result is never actually invoked
2021-12-14 23:50:08 +00:00
} else if (requestCode == PREVIEW_EXPORT_REQUEST_CODE) {
2022-11-28 21:35:12 +00:00
try {
val sourcePath = Paths.get(this.exportFromPath);
val targetUri = intent!!.getData();
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri!!);
val bytesWritten = Files.copy(sourcePath, os);
Log.d("MainActivity:PREVIEW_EXPORT", "copied " + bytesWritten.toString() + " bytes");
if (bytesWritten != 0L) {
os?.flush();
os?.close();
//Files.delete(sourcePath);
}
} catch (e: Exception) {
Log.d("MainActivity:PREVIEW_EXPORT FAILED", e.toString());
2021-12-15 01:13:13 +00:00
}
2022-03-10 21:29:28 +00:00
} else if (requestCode == PROFILE_EXPORT_REQUEST_CODE ) {
val srcFile = StringBuilder().append(this.applicationContext.cacheDir).append("/").append(this.exportFromPath).toString();
2022-11-28 21:35:12 +00:00
Log.i("MainActivity:EXPORT_PROFILE", "exporting profile: " + srcFile);
try {
val sourcePath = Paths.get(srcFile);
val targetUri = intent!!.getData();
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri!!);
val bytesWritten = Files.copy(sourcePath, os);
Log.d("MainActivity:EXPORT_PROFILE", "copied " + bytesWritten.toString() + " bytes");
if (bytesWritten != 0L) {
os?.flush();
os?.close();
//Files.delete(sourcePath);
}
} catch (e: Exception) {
Log.d("MainActivity:EXPORT_PROFILE FAILED", e.toString());
2022-03-10 21:29:28 +00:00
}
2021-10-01 19:38:06 +00:00
}
2021-09-27 19:53:21 +00:00
}
2021-06-24 23:10:45 +00:00
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
// Note: this methods are invoked on the main thread.
//note to self: ask someone if this does anything ^ea
requestWindowFeature(Window.FEATURE_NO_TITLE)
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, ANDROID_SETTINGS_CHANNEL_NAME).setMethodCallHandler { call, result -> handleAndroidSettings(call, result) }
2021-06-25 00:59:54 +00:00
notificationClickChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK)
shutdownClickChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_SHUTDOWN_CLICK)
andoidSettingsChangeChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, ANDROID_SETTINGS_CHANGE_NAME)
2021-06-24 23:10:45 +00:00
}
// MethodChannel CHANNEL_APP_INFO handler (Flutter Channel for requests for Android environment info)
2021-06-24 23:10:45 +00:00
private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
CALL_APP_INFO -> result.success(getNativeLibDir())
?: result.error("Unavailable", "nativeLibDir not available", null);
CALL_SERVICE_INFO -> result.success(getForegroundServiceInfo())
2023-09-11 20:45:17 +00:00
?: result.error("Unavailable", "getForegroundServiceInfo not available", null);
else -> result.notImplemented()
}
}
// MethodChannel ANDROID_SETTINGS_CHANNEL_NAME handler (Flutter Channel for requests for Android settings)
// Called from lib/view/globalsettingsview.dart
private fun handleAndroidSettings(@NonNull call: MethodCall, @NonNull result: Result) {
when (call.method) {
CALL_IS_BATTERY_EXEMPT -> result.success(checkIgnoreBatteryOpt() ?: false);
CALL_ASK_BATTERY_EXEMPTION -> { requestBatteryExemption(); result.success(null); }
2021-06-24 23:10:45 +00:00
else -> result.notImplemented()
}
}
@TargetApi(23)
private fun checkIgnoreBatteryOpt(): Boolean {
val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
return powerManager.isIgnoringBatteryOptimizations(this.packageName) ?: false;
}
@TargetApi(23)
private fun requestBatteryExemption() {
val i = Intent()
i.action = ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
i.data = Uri.parse("package:" + this.packageName)
startActivityForResult(i, REQUEST_DOZE_WHITELISTING_CODE);
}
2021-06-24 23:10:45 +00:00
private fun getNativeLibDir(): String {
val ainfo = this.applicationContext.packageManager.getApplicationInfo(
"im.cwtch.flwtch", // Must be app name
PackageManager.GET_SHARED_LIBRARY_FILES)
return ainfo.nativeLibraryDir
}
fun getForegroundServiceInfo(): HashMap<String,String> {
var hashMap : HashMap<String, String>
= HashMap<String, String> ();
val workmanager = WorkManager.getInstance(this);
if (workmanager != null) {
runBlocking {
var lastCancelAllTime = workmanager.getLastCancelAllTimeMillis().await();
hashMap.put("workmanager.lastCancelAllTime", lastCancelAllTime.toString());
}
val works = workmanager.getWorkInfosByTag(WORKER_TAG).get()
for (workInfo in works) {
hashMap.put("workmanager.${workInfo.id}.state", workInfo.state.toString());
}
}
return hashMap
}
2021-06-24 23:10:45 +00:00
// receives messages from the ForegroundService (which provides, ironically enough, the backend)
private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) {
var method = call.method
// todo change usage patern to match that in FlwtchWorker
// Unsafe for anything using int args, causes access time attempt to cast to string which will fail
2022-12-12 23:10:07 +00:00
val argmap: Map<String, String> = call.arguments as Map<String,String>
2021-06-24 23:10:45 +00:00
// the frontend calls Start every time it fires up, but we don't want to *actually* call Cwtch.Start()
// in case the ForegroundService is still running. in both cases, however, we *do* want to re-register
// the eventbus listener.
when (call.method) {
"Start" -> {
val uniqueTag = argmap["torPath"] ?: "nullEventBus"
2021-06-24 23:10:45 +00:00
// 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(WORKER_TAG).get()
for (workInfo in works) {
WorkManager.getInstance(this).cancelWorkById(workInfo.id)
}
WorkManager.getInstance(this).pruneWork()
2021-06-24 23:10:45 +00:00
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(WORKER_TAG).addTag(uniqueTag).build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.REPLACE, workRequest)
2021-09-27 19:53:21 +00:00
}
"CreateDownloadableFile" -> {
this.dlToProfile = argmap["ProfileOnion"] ?: ""
2022-12-12 23:10:07 +00:00
this.dlToHandle = call.argument("conversation")!!
this.dlManifestPath = argmap["manifestpath"] ?: ""
val suggestedName = argmap["filename"] ?: "filename.ext"
this.dlToFileKey = argmap["filekey"] ?: ""
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/octet-stream"
putExtra(Intent.EXTRA_TITLE, suggestedName)
}
startActivityForResult(intent, FILEPICKER_REQUEST_CODE)
2021-12-14 23:50:08 +00:00
}
"ExportPreviewedFile" -> {
this.exportFromPath = argmap["Path"] ?: ""
val suggestion = argmap["FileName"] ?: "filename.ext"
var imgType = "jpeg"
if (suggestion.endsWith("png")) {
imgType = "png"
} else if (suggestion.endsWith("webp")) {
imgType = "webp"
} else if (suggestion.endsWith("bmp")) {
imgType = "bmp"
} else if (suggestion.endsWith("gif")) {
imgType = "gif"
}
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/" + imgType
putExtra(Intent.EXTRA_TITLE, suggestion)
}
startActivityForResult(intent, PREVIEW_EXPORT_REQUEST_CODE)
2021-12-14 23:50:08 +00:00
}
"ExportProfile" -> {
2022-11-28 21:35:12 +00:00
val profileOnion: String = call.argument("ProfileOnion") ?: ""
val file: String = StringBuilder().append(this.applicationContext.cacheDir).append("/").append(call.argument("file") ?: "").toString()
Log.i("FlwtchWorker", "constructing exported file " + file);
Cwtch.exportProfile(profileOnion,file)
this.exportFromPath = argmap["file"] ?: ""
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/gzip"
putExtra(Intent.EXTRA_TITLE, argmap["file"])
}
startActivityForResult(intent, PROFILE_EXPORT_REQUEST_CODE)
2022-03-10 21:29:28 +00:00
}
"GetMessages" -> {
Log.d("MainActivity.kt", "Cwtch GetMessages")
2021-06-24 23:10:45 +00:00
val profile = argmap["ProfileOnion"] ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val indexI: Int = call.argument("index") ?: 0
val count: Int = call.argument("count") ?: 1
val ucount : Int = maxOf(1, count) // don't allow negative counts
result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), ucount.toLong()))
return
}
"SendMessage" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val message: String = call.argument("message") ?: ""
result.success(Cwtch.sendMessage(profile, conversation.toLong(), message))
return
2021-06-24 23:10:45 +00:00
}
"SendInvitation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val target: Int = call.argument("target") ?: 0
result.success(Cwtch.sendInviteMessage(profile, conversation.toLong(), target.toLong()))
return
}
2022-07-06 20:57:35 +00:00
"ShareFile" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val filepath: String = call.argument("filepath") ?: ""
result.success(Cwtch.shareFile(profile, conversation.toLong(), filepath))
return
}
2022-07-06 20:57:35 +00:00
"GetSharedFiles" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
result.success(Cwtch.getSharedFiles(profile, conversation.toLong()))
return
}
"RestartFileShare" -> {
2022-07-06 20:57:35 +00:00
val profile: String = call.argument("ProfileOnion") ?: ""
val filepath: String = call.argument("filekey") ?: ""
result.success(Cwtch.restartFileShare(profile, filepath))
2022-07-06 20:57:35 +00:00
return
}
"StopSharing" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val filepath: String = call.argument("filekey") ?: ""
result.success(Cwtch.stopFileShare(profile, filepath))
2022-07-06 20:57:35 +00:00
return
}
"DeleteServerInfo" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val handle: String = call.argument("handle") ?: ""
result.success(Cwtch.deleteServerInfo(profile, handle))
return
}
"PublishServerUpdate" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
result.success(Cwtch.publishServerUpdate(profile))
return
}
"PeerWithOnion" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val onion: String = call.argument("onion") ?: ""
Cwtch.peerWithOnion(profile, onion)
}
2023-09-14 01:40:04 +00:00
"QueueJoinServer" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val onion: String = call.argument("onion") ?: ""
Cwtch.peerWithOnion(profile, onion)
}
"DisconnectFromPeer" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val onion: String = call.argument("onion") ?: ""
Cwtch.peerWithOnion(profile, onion)
}
"DisconnectFromServer" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val onion: String = call.argument("onion") ?: ""
Cwtch.peerWithOnion(profile, onion)
}
"CreateProfile" -> {
val nick: String = call.argument("nick") ?: ""
val pass: String = call.argument("pass") ?: ""
2022-12-11 23:14:05 +00:00
val autostart: Boolean = call.argument("autostart") ?: true
2022-12-11 01:21:08 +00:00
Cwtch.createProfile(nick, pass, autostart)
}
"LoadProfiles" -> {
val pass: String = call.argument("pass") ?: ""
Cwtch.loadProfiles(pass)
}
"ActivatePeerEngine" -> {
val profile: String = call.argument("profile") ?: ""
Cwtch.activatePeerEngine(profile)
}
2023-09-14 01:38:08 +00:00
"ConfigureConnections" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
2023-09-14 01:38:08 +00:00
val listen: Boolean = call.argument("listen") ?: false
val peers: Boolean = call.argument("peers") ?: false
val servers: Boolean = call.argument("servers") ?: false
Cwtch.configureConnections(profile, listen, peers, servers)
}
"DeactivatePeerEngine" -> {
val profile: String = call.argument("profile") ?: ""
Cwtch.deactivatePeerEngine(profile)
}
"ChangePassword" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val pass: String = call.argument("OldPass") ?: ""
val passNew: String = call.argument("NewPass") ?: ""
val passNew2: String = call.argument("NewPassAgain") ?: ""
Cwtch.changePassword(profile, pass, passNew, passNew2)
}
"GetMessageByID" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val id: Int = call.argument("id") ?: 0
result.success(Cwtch.getMessageById(profile, conversation.toLong(), id.toLong()))
return
}
"GetMessageByContentHash" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val contentHash: String = call.argument("contentHash") ?: ""
result.success(Cwtch.getMessageByContentHash(profile, conversation.toLong(), contentHash))
return
}
"SetMessageAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val channel: Int = call.argument("Chanenl") ?: 0
val midx: Int = call.argument("Message") ?: 0
val key: String = call.argument("key") ?: ""
val value: String = call.argument("value") ?: ""
Cwtch.updateMessageAttribute(profile, conversation.toLong(), channel.toLong(), midx.toLong(), key, value)
}
"AcceptConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.acceptConversation(profile, conversation.toLong())
}
"BlockContact" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.blockConversation(profile, conversation.toLong())
}
"UnblockContact" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.unblockConversation(profile, conversation.toLong())
}
"DownloadFile" -> {
2022-12-12 23:10:07 +00:00
Log.d("MainActivity.kt", "Cwtch Download File Called...")
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val filepath: String = call.argument("filepath") ?: ""
val manifestpath: String = call.argument("manifestpath") ?: ""
val filekey: String = call.argument("filekey") ?: ""
// FIXME: Prevent spurious calls by Intent
if (profile != "") {
Cwtch.downloadFileDefaultLimit(profile, conversation.toLong(), filepath, manifestpath, filekey)
}
}
"CheckDownloadStatus" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val fileKey: String = call.argument("fileKey") ?: ""
Cwtch.checkDownloadStatus(profile, fileKey)
}
"VerifyOrResumeDownload" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val fileKey: String = call.argument("fileKey") ?: ""
2023-04-18 21:09:36 +00:00
Cwtch.verifyOrResumeDownloadDefaultLimit(profile, conversation.toLong(), fileKey)
}
"UpdateSettings" -> {
val json: String = call.argument("json") ?: ""
Cwtch.updateSettings(json)
}
"ResetTor" -> {
Cwtch.resetTor()
}
"ImportBundle" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val bundle: String = call.argument("bundle") ?: ""
2022-08-16 05:33:09 +00:00
result.success(Cwtch.importBundle(profile, bundle))
}
"CreateGroup" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val server: String = call.argument("server") ?: ""
val groupName: String = call.argument("groupName") ?: ""
2023-09-25 21:05:44 +00:00
Cwtch.startGroup(profile, groupName, server)
}
"DeleteProfile" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val pass: String = call.argument("pass") ?: ""
Cwtch.deleteProfile(profile, pass)
}
"ArchiveConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.archiveConversation(profile, conversation.toLong())
}
"DeleteConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.deleteConversation(profile, conversation.toLong())
}
"SetProfileAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val key: String = call.argument("Key") ?: ""
val v: String = call.argument("Val") ?: ""
Cwtch.setProfileAttribute(profile, key, v)
}
2023-04-04 20:58:42 +00:00
"GetProfileAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val key: String = call.argument("key") ?: ""
var resultjson = Cwtch.getProfileAttribute(profile, key);
return result.success(resultjson)
2023-04-04 20:58:42 +00:00
}
"GetConversationAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val key: String = call.argument("key") ?: ""
var resultjson = Cwtch.getConversationAttribute(profile, conversation.toLong(), key);
result.success(resultjson)
return
2023-04-04 20:58:42 +00:00
}
"SetConversationAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val key: String = call.argument("Key") ?: ""
val v: String = call.argument("Val") ?: ""
Cwtch.setConversationAttribute(profile, conversation.toLong(), key, v)
}
"ImportProfile" -> {
val file: String = call.argument("file") ?: ""
val pass: String = call.argument("pass") ?: ""
result.success(Cwtch.importProfile(file, pass))
return
}
2023-08-02 16:49:36 +00:00
"SearchConversations" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val pattern: String = call.argument("pattern") ?: ""
result.success(Cwtch.searchConversations(profile, pattern))
return
}
"Shutdown" -> {
Cwtch.shutdownCwtch();
}
else -> {
// ...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()
WorkManager.getInstance(this).enqueue(workRequest)
WorkManager.getInstance(applicationContext).getWorkInfoByIdLiveData(workRequest.id).observe(
this, Observer { workInfo ->
if (workInfo != null && workInfo.state == WorkInfo.State.SUCCEEDED) {
val res = workInfo.outputData.keyValueMap.toString()
result.success(workInfo.outputData.getString("result"))
}
}
)
return
}
}
result.success(null)
2021-06-24 23:10:45 +00:00
}
// 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 local broadcast receiver / event bus forwarder")
val bm = flutterEngine?.dartExecutor?.binaryMessenger;
if (bm != null) {
val mc = MethodChannel(bm, CWTCH_EVENTBUS)
val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS")
myReceiver = MyBroadcastReceiver(mc)
LocalBroadcastManager.getInstance(applicationContext)
.registerReceiver(myReceiver!!, filter)
}
2021-06-24 23:10:45 +00:00
}
// ReconnectCwtchForeground which will resync counters and settings...
// We need to do this here because after a "pause" flutter is still running
// but we might have lost sync with the background process...
Log.i("MainActivity.kt", "Call ReconnectCwtchForeground")
Cwtch.reconnectCwtchForeground()
2021-06-24 23:10:45 +00:00
}
override fun onStop() {
super.onStop()
Log.i("MainActivity.kt", "onStop")
if (myReceiver != null) {
LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(myReceiver!!);
myReceiver = null;
}
}
2022-11-28 21:35:12 +00:00
2021-06-24 23:10:45 +00:00
override fun onDestroy() {
super.onDestroy()
Log.i("MainActivity.kt", "onDestroy - cancelling all WORKER_TAG and pruning old work")
WorkManager.getInstance(this).cancelAllWorkByTag(WORKER_TAG)
WorkManager.getInstance(this).pruneWork()
}
class AppbusEvent(json: String) : JSONObject(json) {
val EventType = this.optString("EventType")
val EventID = this.optString("EventID")
val Data = this.optString("Data")
}
// MainActivity.MyBroadcastReceiver receives events from the Cwtch service via im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS Android local broadcast intents
// then it forwards them to the flutter ui engine using the CWTCH_EVENTBUS methodchannel
class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() {
val eventBus: MethodChannel = mc
override fun onReceive(context: Context, intent: Intent) {
val evtType = intent.getStringExtra("EventType") ?: ""
val evtData = intent.getStringExtra("Data") ?: ""
//val evtID = intent.getStringExtra("EventID") ?: ""//todo?
eventBus.invokeMethod(evtType, evtData)
}
}
}