Skip to content

Commit fb597ad

Browse files
committed
Move to keychain
1 parent d8ee2a8 commit fb597ad

21 files changed

Lines changed: 3499 additions & 571 deletions

File tree

.dart_tool/build/fcd1995bc647fb959e82ea360c6c2c9a/asset_graph.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

android/app/src/main/kotlin/com/bluebubbles/messaging/services/backend_ui_interop/DartWorker.kt

Lines changed: 101 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -35,41 +35,85 @@ import kotlin.concurrent.schedule
3535
import kotlin.coroutines.resume
3636
import kotlin.coroutines.suspendCoroutine
3737
import kotlinx.coroutines.guava.future
38+
import java.util.TimerTask
39+
import java.util.concurrent.atomic.AtomicInteger
3840

3941
class DartWorker(context: Context, workerParams: WorkerParameters): ListenableWorker(context, workerParams) {
4042

4143
companion object {
4244
var workerEngine: FlutterEngine? = null
4345
var engineReady = Mutex()
44-
}
4546

46-
override fun startWork(): ListenableFuture<Result> {
47-
val method = inputData.getString("method")!!
48-
var data = inputData.getString("data")!!
49-
val gson = GsonBuilder()
50-
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
51-
.create()
52-
if (method == "SMSMsg") {
53-
val json: HashMap<String, Any> = gson.fromJson(data, TypeToken.getParameterized(HashMap::class.java, String::class.java, Any::class.java).type)
54-
val pointer: Int = (json["id"] as Long).toInt()
55-
if (MethodCallHandler.queuedMessages.contains(pointer)) {
56-
data = MethodCallHandler.queuedMessages.remove(pointer)!!
57-
} else {
58-
// bail
59-
return Futures.immediateFuture(Result.success())
47+
48+
49+
/// Code idea taken from https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
50+
private suspend fun initNewEngine(applicationContext: Context) {
51+
Log.d(Constants.logTag, "Ensuring Flutter is initialized before creating engine")
52+
// We use the deprecated class here anyways, the new one doesn't work correctly using the same code
53+
FlutterMain.startInitialization(applicationContext)
54+
FlutterMain.ensureInitializationComplete(applicationContext, null)
55+
56+
Log.d(Constants.logTag, "Loading callback info")
57+
val info = ApplicationInfoLoader.load(applicationContext)
58+
workerEngine = FlutterEngine(applicationContext)
59+
60+
currentJobs.set(0)
61+
62+
workerEngine!!.addEngineLifecycleListener ( object : FlutterEngine.EngineLifecycleListener {
63+
override fun onPreEngineRestart() {
64+
Log.d(Constants.logTag, "Engine is restarting")
65+
}
66+
67+
override fun onEngineWillDestroy() {
68+
Log.d(Constants.logTag, "Engine is being destroyed")
69+
}
70+
})
71+
suspendCoroutine { cont ->
72+
// set up the method channel to receive events from Dart
73+
MethodChannel(workerEngine!!.dartExecutor.binaryMessenger, Constants.methodChannel).setMethodCallHandler {
74+
call, result -> run {
75+
if (call.method == "ready") {
76+
Log.d(Constants.logTag, "Dart engine is ready!")
77+
cont.resume(Unit)
78+
} else {
79+
MethodCallHandler().methodCallHandler(call, result, applicationContext)
80+
}
81+
}
82+
}
83+
val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(applicationContext.getSharedPreferences("FlutterSharedPreferences", 0).getLong("flutter.backgroundCallbackHandle", -1))
84+
val callback = DartExecutor.DartCallback(applicationContext.assets, info.flutterAssetsDir, callbackInfo)
85+
86+
Log.d(Constants.logTag, "Executing Dart callback")
87+
workerEngine!!.dartExecutor.executeDartCallback(callback)
6088
}
6189
}
6290

63-
if (engine != null) {
64-
Log.d(Constants.logTag, "Using MainActivity engine to send to Dart")
65-
} else {
66-
Log.d(Constants.logTag, "Using DartWorker engine to send to Dart")
91+
var currentCancelTask: TimerTask? = null
92+
private fun closeEngineIfNeeded(applicationContext: Context) {
93+
currentJobs.getAndDecrement()
94+
// Delay 30 seconds so Dart has a chance to complete everything and in case new work comes in shortly after
95+
currentCancelTask?.cancel()
96+
currentCancelTask = Timer().schedule(30000) {
97+
currentCancelTask = null
98+
Log.d(Constants.logTag, "$currentJobs worker(s) still queued")
99+
if (currentJobs.get() == 0 && workerEngine != null) {
100+
Log.d(Constants.logTag, "Closing ${Constants.dartWorkerTag} engine")
101+
// This must be run on main thread
102+
CoroutineScope(Dispatchers.Main).launch {
103+
workerEngine?.destroy()
104+
workerEngine = null
105+
}
106+
}
107+
}
67108
}
68-
return CoroutineScope(Dispatchers.Main).future {
109+
110+
var currentJobs = AtomicInteger(0)
111+
112+
suspend fun callMethod(applicationContext: Context, method: String, arguments: Map<String, Any>) {
69113
engineReady.withLock {
70114
if (engine == null && workerEngine == null) {
71115
Log.d(Constants.logTag, "Initializing engine for worker with method $method")
72-
initNewEngine()
116+
initNewEngine(applicationContext)
73117
}
74118
}
75119
Log.d(Constants.logTag, "Sending event, '$method' to Dart")
@@ -78,95 +122,73 @@ class DartWorker(context: Context, workerParams: WorkerParameters): ListenableWo
78122
var engineToUse: FlutterEngine? = engine ?: workerEngine
79123
if (engineToUse == null) {
80124
Log.d(Constants.logTag, "Engine is null, cannot send method $method to Dart")
81-
return@future Result.failure()
125+
throw Exception("No engine")
82126
}
83127

84128
Log.d(Constants.logTag, "Registering engine lifecycle listener")
85129

86-
engineToUse!!.addEngineLifecycleListener ( object : FlutterEngine.EngineLifecycleListener {
87-
override fun onPreEngineRestart() {
88-
Log.d(Constants.logTag, "Engine is restarting")
89-
}
90-
91-
override fun onEngineWillDestroy() {
92-
Log.d(Constants.logTag, "Engine is being destroyed")
93-
}
94-
})
95-
96130
Log.d(Constants.logTag, "Invoking method channel...")
97131
suspendCoroutine { cont ->
98-
MethodChannel(engineToUse!!.dartExecutor.binaryMessenger, Constants.methodChannel).invokeMethod(method, gson.fromJson(data, TypeToken.getParameterized(HashMap::class.java, String::class.java, Any::class.java).type), object : MethodChannel.Result {
132+
currentJobs.getAndIncrement()
133+
MethodChannel(engineToUse!!.dartExecutor.binaryMessenger, Constants.methodChannel).invokeMethod(method, arguments, object : MethodChannel.Result {
99134
override fun success(result: Any?) {
100135
Log.d(Constants.logTag, "Worker with method $method completed successfully")
101136
cont.resume(Result.success())
102-
closeEngineIfNeeded()
137+
closeEngineIfNeeded(applicationContext)
103138
}
104-
139+
105140
override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) {
106141
Log.e(Constants.logTag, "Worker with method $method failed!")
107142
cont.resume(Result.failure())
108-
closeEngineIfNeeded()
143+
closeEngineIfNeeded(applicationContext)
109144
}
110-
145+
111146
override fun notImplemented() {
112147
Log.e(Constants.logTag, "Worker with method $method not implemented on Dart side")
113148
cont.resume(Result.failure())
114-
closeEngineIfNeeded()
149+
closeEngineIfNeeded(applicationContext)
115150
}
116151
})
117152
}
118153

119154
Log.d(Constants.logTag, "Worker with method $method completed successfully")
120-
return@future Result.success()
121155
} catch (e: Exception) {
122156
Log.d(Constants.logTag, "Error sending method $method to Dart: ${e.message}")
123-
return@future Result.failure()
157+
throw e
124158
}
125159
}
126160
}
127161

128-
/// Code idea taken from https://github.com/flutter/flutter/wiki/Experimental:-Reuse-FlutterEngine-across-screens
129-
private suspend fun initNewEngine() {
130-
Log.d(Constants.logTag, "Ensuring Flutter is initialized before creating engine")
131-
// We use the deprecated class here anyways, the new one doesn't work correctly using the same code
132-
FlutterMain.startInitialization(applicationContext)
133-
FlutterMain.ensureInitializationComplete(applicationContext, null)
134-
135-
Log.d(Constants.logTag, "Loading callback info")
136-
val info = ApplicationInfoLoader.load(applicationContext)
137-
workerEngine = FlutterEngine(applicationContext)
138-
suspendCoroutine { cont ->
139-
// set up the method channel to receive events from Dart
140-
MethodChannel(workerEngine!!.dartExecutor.binaryMessenger, Constants.methodChannel).setMethodCallHandler {
141-
call, result -> run {
142-
if (call.method == "ready") {
143-
Log.d(Constants.logTag, "Dart engine is ready!")
144-
cont.resume(Unit)
145-
} else {
146-
MethodCallHandler().methodCallHandler(call, result, applicationContext)
147-
}
148-
}
162+
override fun startWork(): ListenableFuture<Result> {
163+
val method = inputData.getString("method")!!
164+
var data = inputData.getString("data")!!
165+
val gson = GsonBuilder()
166+
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
167+
.create()
168+
if (method == "SMSMsg") {
169+
val json: HashMap<String, Any> = gson.fromJson(data, TypeToken.getParameterized(HashMap::class.java, String::class.java, Any::class.java).type)
170+
val pointer: Int = (json["id"] as Long).toInt()
171+
if (MethodCallHandler.queuedMessages.contains(pointer)) {
172+
data = MethodCallHandler.queuedMessages.remove(pointer)!!
173+
} else {
174+
// bail
175+
return Futures.immediateFuture(Result.success())
149176
}
150-
val callbackInfo = FlutterCallbackInformation.lookupCallbackInformation(applicationContext.getSharedPreferences("FlutterSharedPreferences", 0).getLong("flutter.backgroundCallbackHandle", -1))
151-
val callback = DartExecutor.DartCallback(applicationContext.assets, info.flutterAssetsDir, callbackInfo)
152-
153-
Log.d(Constants.logTag, "Executing Dart callback")
154-
workerEngine!!.dartExecutor.executeDartCallback(callback)
155177
}
156-
}
157178

158-
private fun closeEngineIfNeeded() {
159-
// Delay 5 seconds so Dart has a chance to complete everything and in case new work comes in shortly after
160-
Timer().schedule(5000) {
161-
val currentWork = WorkManager.getInstance(applicationContext).getWorkInfosByTag(Constants.dartWorkerTag).get().filter { element -> !element.state.isFinished }
162-
Log.d(Constants.logTag, "${currentWork.size} worker(s) still queued")
163-
if (currentWork.isEmpty() && workerEngine != null) {
164-
Log.d(Constants.logTag, "Closing ${Constants.dartWorkerTag} engine")
165-
// This must be run on main thread
166-
CoroutineScope(Dispatchers.Main).launch {
167-
workerEngine?.destroy()
168-
workerEngine = null
169-
}
179+
if (engine != null) {
180+
Log.d(Constants.logTag, "Using MainActivity engine to send to Dart")
181+
} else {
182+
Log.d(Constants.logTag, "Using DartWorker engine to send to Dart")
183+
}
184+
return CoroutineScope(Dispatchers.Main).future {
185+
val arguments: HashMap<String, Any> = gson.fromJson(data, TypeToken.getParameterized(HashMap::class.java, String::class.java, Any::class.java).type)
186+
try {
187+
callMethod(applicationContext, method, arguments)
188+
Result.success()
189+
} catch (e: Exception) {
190+
Log.d(Constants.logTag, "Error sending method $method to Dart: ${e.message}")
191+
Result.failure()
170192
}
171193
}
172194
}

android/app/src/main/kotlin/com/bluebubbles/messaging/services/backend_ui_interop/MethodCallHandler.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import com.bluebubbles.messaging.services.notifications.CreateMissedFaceTimeNoti
4141
import com.bluebubbles.messaging.services.rustpush.AppleAccountLoginHandler
4242
import com.bluebubbles.messaging.services.rustpush.EAPAKAGateway
4343
import com.bluebubbles.messaging.services.rustpush.GetNativeHandleHandler
44+
import com.bluebubbles.messaging.services.rustpush.KeystoreUnlockHandler
4445
import com.bluebubbles.messaging.services.rustpush.ProvisionNative
4546
import com.bluebubbles.messaging.services.rustpush.SMSLessAuthGateway
4647
import com.bluebubbles.messaging.services.system.EnableBTHandler
@@ -139,6 +140,7 @@ class MethodCallHandler {
139140
ShizukuGrantPermissionHandler.tag -> ShizukuGrantPermissionHandler().handleMethodCall(call, result, context)
140141
ProvisionNative.tag -> ProvisionNative().handleMethodCall(call, result, context)
141142
EAPAKAGateway.tag -> EAPAKAGateway().handleMethodCall(call, result, context)
143+
KeystoreUnlockHandler.tag -> KeystoreUnlockHandler().handleMethodCall(call, result, context)
142144
"ready" -> { MainActivity.engine_ready = true }
143145
else -> {
144146
val error = "Could not find method call handler for ${call.method}!"

android/app/src/main/kotlin/com/bluebubbles/messaging/services/rustpush/APNService.kt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.core.content.ContextCompat
2222
import com.bluebubbles.messaging.MainActivity
2323
import com.bluebubbles.messaging.R
2424
import com.bluebubbles.messaging.services.backend_ui_interop.DartWorkManager
25+
import com.bluebubbles.messaging.services.backend_ui_interop.DartWorker
2526
import com.bluebubbles.messaging.services.backend_ui_interop.MethodCallHandler
2627
import com.bluebubbles.messaging.services.system.GetZenMode
2728
import com.bluebubbles.messaging.services.system.ZenModeUUIDHandler
@@ -31,9 +32,11 @@ import com.google.gson.ToNumberPolicy
3132
import kotlinx.coroutines.CoroutineScope
3233
import kotlinx.coroutines.Dispatchers
3334
import kotlinx.coroutines.SupervisorJob
35+
import kotlinx.coroutines.launch
3436
import uniffi.rust_lib_bluebubbles.NativePushState
3537
import uniffi.rust_lib_bluebubbles.initNative
3638
import uniffi.rust_lib_bluebubbles.MsgReceiver
39+
import uniffi.rust_lib_bluebubbles.setupKeystore
3740
import uniffi.rust_lib_bluebubbles.start
3841

3942
class APNService : Service(), MsgReceiver {
@@ -71,7 +74,9 @@ class APNService : Service(), MsgReceiver {
7174
return@post
7275
}
7376
Log.i("ugh running", "backend $ptr $retry")
74-
DartWorkManager.createWorker(this@APNService, "APNMsg", hashMapOf("pointer" to ptr.toString(), "retry" to retry.toString())) {}
77+
CoroutineScope(Dispatchers.Main).launch {
78+
DartWorker.callMethod(this@APNService, "APNMsg", mapOf("pointer" to ptr.toString(), "retry" to retry.toString()))
79+
}
7580
}
7681
}
7782

@@ -114,6 +119,7 @@ class APNService : Service(), MsgReceiver {
114119
pushState?.publishStatus(uuid)
115120
}
116121

122+
val keystore = AndroidNativeKeystore(this)
117123

118124
fun launchAgent() {
119125
Log.i("launching agent", "herer")
@@ -123,12 +129,9 @@ class APNService : Service(), MsgReceiver {
123129
MethodCallHandler.invokeMethod("SMSMsg", map)
124130
return@init
125131
}
126-
MethodCallHandler.queueId += 1
127-
val gson = GsonBuilder()
128-
.setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)
129-
.create()
130-
MethodCallHandler.queuedMessages[MethodCallHandler.queueId] = gson.toJson(map).toString()
131-
DartWorkManager.createWorker(context, "SMSMsg", hashMapOf("id" to MethodCallHandler.queueId)) {}
132+
CoroutineScope(Dispatchers.Main).launch {
133+
DartWorker.callMethod(this@APNService, "SMSMsg", map)
134+
}
132135
}
133136

134137
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
@@ -151,8 +154,14 @@ class APNService : Service(), MsgReceiver {
151154
)
152155
}
153156

157+
Log.i("here", "hjeal")
158+
154159
start(applicationContext.filesDir.path, AndroidFilePackager(this))
160+
setupKeystore(applicationContext.filesDir.path, keystore)
161+
keystore.checkMaster()
162+
Log.i("here", "hjealme")
155163

164+
Log.i("here", "hwallow")
156165
initNative(applicationContext.filesDir.path, null, this)
157166
}
158167

0 commit comments

Comments
 (0)