gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-taler-android] 01/02: [wallet] Don't run qtart in a separate proc


From: gnunet
Subject: [taler-taler-android] 01/02: [wallet] Don't run qtart in a separate process
Date: Tue, 28 Mar 2023 18:26:49 +0200

This is an automated email from the git hooks/post-receive script.

torsten-grote pushed a commit to branch master
in repository taler-android.

commit ffe67691fe7ad995113eacb0ee5785f51e0051de
Author: Torsten Grote <t@grobox.de>
AuthorDate: Tue Sep 27 17:56:05 2022 -0300

    [wallet] Don't run qtart in a separate process
    
    This required IPC and the mechanism chosen was limiting us to transferring 
1MB (or less!) to/from wallet-core. Now we simply run it in an IO thread. The 
should be no functional difference (except new bugs introduced when swapping in 
a new mechanism). The second process with qtart running in WalletBackendService 
also got killed when the OS killed our main process.
---
 wallet/src/main/AndroidManifest.xml                |   4 -
 .../src/main/java/net/taler/wallet/MainActivity.kt |   1 -
 .../main/java/net/taler/wallet/MainViewModel.kt    |  74 +++---
 wallet/src/main/java/net/taler/wallet/WalletApp.kt |   2 +-
 .../java/net/taler/wallet/backend/ApiResponse.kt   |  61 +++++
 .../net/taler/wallet/backend/BackendManager.kt     |  89 +++++++
 .../{WalletApp.kt => backend/InitResponse.kt}      |  27 ++-
 .../{WalletApp.kt => backend/RequestManager.kt}    |  31 ++-
 .../net/taler/wallet/backend/TalerErrorCode.kt     |   5 +-
 .../net/taler/wallet/backend/WalletBackendApi.kt   | 175 ++++----------
 .../taler/wallet/backend/WalletBackendService.kt   | 266 ---------------------
 .../wallet/pending/PendingOperationsManager.kt     |  45 ++--
 12 files changed, 307 insertions(+), 473 deletions(-)

diff --git a/wallet/src/main/AndroidManifest.xml 
b/wallet/src/main/AndroidManifest.xml
index 0b0a5b6..96c2958 100644
--- a/wallet/src/main/AndroidManifest.xml
+++ b/wallet/src/main/AndroidManifest.xml
@@ -89,10 +89,6 @@
                 android:name="android.nfc.cardemulation.host_apdu_service"
                 android:resource="@xml/apduservice" />
         </service>
-
-        <service
-            android:name=".backend.WalletBackendService"
-            android:process=":WalletBackendService" />
     </application>
 
     <queries>
diff --git a/wallet/src/main/java/net/taler/wallet/MainActivity.kt 
b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
index 7a40b4b..e91b983 100644
--- a/wallet/src/main/java/net/taler/wallet/MainActivity.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainActivity.kt
@@ -70,7 +70,6 @@ import java.net.URL
 import java.util.Locale.ROOT
 import javax.net.ssl.HttpsURLConnection
 
-
 class MainActivity : AppCompatActivity(), OnNavigationItemSelectedListener,
     OnPreferenceStartFragmentCallback {
 
diff --git a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt 
b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
index ed12533..bbd3ca3 100644
--- a/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
+++ b/wallet/src/main/java/net/taler/wallet/MainViewModel.kt
@@ -24,15 +24,19 @@ import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.distinctUntilChanged
 import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 import net.taler.common.Amount
 import net.taler.common.AmountParserException
 import net.taler.common.Event
-import net.taler.common.assertUiThread
 import net.taler.common.toEvent
 import net.taler.wallet.accounts.AccountManager
+import net.taler.wallet.backend.NotificationPayload
+import net.taler.wallet.backend.NotificationReceiver
+import net.taler.wallet.backend.VersionReceiver
 import net.taler.wallet.backend.WalletBackendApi
+import net.taler.wallet.backend.WalletCoreVersion
 import net.taler.wallet.balances.BalanceItem
 import net.taler.wallet.balances.BalanceResponse
 import net.taler.wallet.deposit.DepositManager
@@ -58,7 +62,9 @@ private val transactionNotifications = listOf(
     "withdraw-group-finished"
 )
 
-class MainViewModel(val app: Application) : AndroidViewModel(app) {
+class MainViewModel(
+    app: Application,
+) : AndroidViewModel(app), VersionReceiver, NotificationReceiver {
 
     private val mBalances = MutableLiveData<List<BalanceItem>>()
     val balances: LiveData<List<BalanceItem>> = 
mBalances.distinctUntilChanged()
@@ -70,32 +76,13 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     var merchantVersion: String? = null
         private set
 
-    private val api = WalletBackendApi(app) { payload ->
-        if (payload.optString("operation") == "init") {
-            val result = payload.getJSONObject("result")
-            val versions = result.getJSONObject("versionInfo")
-            exchangeVersion = versions.getString("exchange")
-            merchantVersion = versions.getString("merchant")
-        } else if (payload.getString("type") != "waiting-for-retry") { // 
ignore ping
-            Log.i(TAG, "Received notification from wallet-core: 
${payload.toString(2)}")
-            loadBalances()
-            if (payload.optString("type") in transactionNotifications) {
-                assertUiThread()
-                // TODO notification API should give us a currency to update
-                // update currently selected transaction list
-                transactionManager.loadTransactions()
-            }
-            // refresh pending ops and history with each notification
-            if (devMode.value == true) {
-                pendingOperationsManager.getPending()
-            }
-        }
-    }
+    private val api = WalletBackendApi(app, this, this)
 
     val withdrawManager = WithdrawManager(api, viewModelScope)
     val tipManager = TipManager(api, viewModelScope)
     val paymentManager = PaymentManager(api, viewModelScope)
-    val pendingOperationsManager: PendingOperationsManager = 
PendingOperationsManager(api)
+    val pendingOperationsManager: PendingOperationsManager =
+        PendingOperationsManager(api, viewModelScope)
     val transactionManager: TransactionManager = TransactionManager(api, 
viewModelScope)
     val refundManager = RefundManager(api, viewModelScope)
     val exchangeManager: ExchangeManager = ExchangeManager(api, viewModelScope)
@@ -117,9 +104,25 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
     )
     val lastBackup: LiveData<Long> = mLastBackup
 
-    override fun onCleared() {
-        api.destroy()
-        super.onCleared()
+    override fun onVersionReceived(versionInfo: WalletCoreVersion) {
+        exchangeVersion = versionInfo.exchange
+        merchantVersion = versionInfo.merchant
+    }
+
+    override fun onNotificationReceived(payload: NotificationPayload) {
+        if (payload.type == "waiting-for-retry") return // ignore ping)
+        Log.i(TAG, "Received notification from wallet-core: $payload")
+
+        loadBalances()
+        if (payload.type in transactionNotifications) 
viewModelScope.launch(Dispatchers.Main) {
+            // TODO notification API should give us a currency to update
+            // update currently selected transaction list
+            transactionManager.loadTransactions()
+        }
+        // refresh pending ops and history with each notification
+        if (devMode.value == true) {
+            pendingOperationsManager.getPending()
+        }
     }
 
     @UiThread
@@ -174,22 +177,29 @@ class MainViewModel(val app: Application) : 
AndroidViewModel(app) {
 
     @UiThread
     fun dangerouslyReset() {
-        api.sendRequest("reset")
+        viewModelScope.launch {
+            api.sendRequest("reset")
+        }
         withdrawManager.testWithdrawalStatus.value = null
         mBalances.value = emptyList()
     }
 
     fun startTunnel() {
-        api.sendRequest("startTunnel")
+        viewModelScope.launch {
+            api.sendRequest("startTunnel")
+        }
     }
 
     fun stopTunnel() {
-        api.sendRequest("stopTunnel")
+        viewModelScope.launch {
+            api.sendRequest("stopTunnel")
+        }
     }
 
     fun tunnelResponse(resp: String) {
-        val respJson = JSONObject(resp)
-        api.sendRequest("tunnelResponse", respJson)
+        viewModelScope.launch {
+            api.sendRequest("tunnelResponse", JSONObject(resp))
+        }
     }
 
     @UiThread
diff --git a/wallet/src/main/java/net/taler/wallet/WalletApp.kt 
b/wallet/src/main/java/net/taler/wallet/WalletApp.kt
index 1076364..1384f76 100644
--- a/wallet/src/main/java/net/taler/wallet/WalletApp.kt
+++ b/wallet/src/main/java/net/taler/wallet/WalletApp.kt
@@ -19,7 +19,7 @@ package net.taler.wallet
 import android.app.Application
 import com.google.android.material.color.DynamicColors
 
-class WalletApp: Application() {
+class WalletApp : Application() {
     override fun onCreate() {
         super.onCreate()
         DynamicColors.applyToActivitiesIfAvailable(this)
diff --git a/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt 
b/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt
new file mode 100644
index 0000000..46eb2f0
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/backend/ApiResponse.kt
@@ -0,0 +1,61 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2023 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.backend
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonObject
+
+@Serializable
+sealed class ApiMessage {
+
+    @Serializable
+    @SerialName("notification")
+    data class Notification(
+        val payload: NotificationPayload,
+    ) : ApiMessage()
+
+}
+
+@Serializable
+data class NotificationPayload(
+    val type: String,
+    val id: String? = null,
+)
+
+@Serializable
+sealed class ApiResponse : ApiMessage() {
+
+    abstract val id: Int
+    abstract val operation: String
+
+    @Serializable
+    @SerialName("response")
+    data class Response(
+        override val id: Int,
+        override val operation: String,
+        val result: JsonObject,
+    ) : ApiResponse()
+
+    @Serializable
+    @SerialName("error")
+    data class Error(
+        override val id: Int,
+        override val operation: String,
+        val error: JsonObject,
+    ) : ApiResponse()
+}
diff --git a/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt 
b/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt
new file mode 100644
index 0000000..ae338e8
--- /dev/null
+++ b/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt
@@ -0,0 +1,89 @@
+/*
+ * This file is part of GNU Taler
+ * (C) 2022 Taler Systems S.A.
+ *
+ * GNU Taler is free software; you can redistribute it and/or modify it under 
the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3, or (at your option) any later version.
+ *
+ * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+ * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+package net.taler.wallet.backend
+
+import android.util.Log
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
+import net.taler.qtart.TalerWalletCore
+import net.taler.wallet.BuildConfig
+import org.json.JSONObject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
+
+
+fun interface NotificationReceiver {
+    fun onNotificationReceived(payload: NotificationPayload)
+}
+
+class BackendManager(
+    private val notificationReceiver: NotificationReceiver,
+) {
+
+    companion object {
+        private const val TAG = "BackendManager"
+        private const val TAG_CORE = "taler-wallet-embedded"
+        val json = Json {
+            ignoreUnknownKeys = true
+        }
+    }
+
+    private val walletCore = TalerWalletCore()
+    private val requestManager = RequestManager()
+
+    init {
+        walletCore.setMessageHandler { onMessageReceived(it) }
+        if (BuildConfig.DEBUG) walletCore.setStdoutHandler {
+            Log.d(TAG_CORE, it)
+        }
+    }
+
+    fun run() {
+        walletCore.run()
+    }
+
+    suspend fun send(operation: String, args: JSONObject? = null): ApiResponse 
=
+        suspendCoroutine { cont ->
+            requestManager.addRequest(cont) { id ->
+                val request = JSONObject().apply {
+                    put("id", id)
+                    put("operation", operation)
+                    if (args != null) put("args", args)
+                }
+                Log.d(TAG, "sending message:\n${request.toString(2)}")
+                walletCore.sendRequest(request.toString())
+            }
+        }
+
+    private fun onMessageReceived(msg: String) {
+        Log.d(TAG, "message received: $msg")
+        when (val message = json.decodeFromString<ApiMessage>(msg)) {
+            is ApiMessage.Notification -> {
+                notificationReceiver.onNotificationReceived(message.payload)
+            }
+            is ApiResponse -> {
+                val id = message.id
+                val cont = requestManager.getAndRemoveContinuation(id)
+                if (cont == null) {
+                    Log.e(TAG, "wallet returned unknown request ID ($id)")
+                } else {
+                    cont.resume(message)
+                }
+            }
+        }
+    }
+}
diff --git a/wallet/src/main/java/net/taler/wallet/WalletApp.kt 
b/wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt
similarity index 61%
copy from wallet/src/main/java/net/taler/wallet/WalletApp.kt
copy to wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt
index 1076364..076af87 100644
--- a/wallet/src/main/java/net/taler/wallet/WalletApp.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/InitResponse.kt
@@ -14,14 +14,25 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet
+package net.taler.wallet.backend
 
-import android.app.Application
-import com.google.android.material.color.DynamicColors
+import kotlinx.serialization.Serializable
 
-class WalletApp: Application() {
-    override fun onCreate() {
-        super.onCreate()
-        DynamicColors.applyToActivitiesIfAvailable(this)
-    }
+@Serializable
+data class InitResponse(
+    val versionInfo: WalletCoreVersion,
+)
+
+fun interface VersionReceiver {
+    fun onVersionReceived(versionInfo: WalletCoreVersion)
 }
+
+@Serializable
+data class WalletCoreVersion(
+    val hash: String? = null,
+    val version: String,
+    val exchange: String,
+    val merchant: String,
+    val bank: String,
+    val devMode: Boolean,
+)
diff --git a/wallet/src/main/java/net/taler/wallet/WalletApp.kt 
b/wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt
similarity index 52%
copy from wallet/src/main/java/net/taler/wallet/WalletApp.kt
copy to wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt
index 1076364..041656e 100644
--- a/wallet/src/main/java/net/taler/wallet/WalletApp.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/RequestManager.kt
@@ -14,14 +14,31 @@
  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet
+package net.taler.wallet.backend
 
-import android.app.Application
-import com.google.android.material.color.DynamicColors
+import androidx.annotation.GuardedBy
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.coroutines.Continuation
 
-class WalletApp: Application() {
-    override fun onCreate() {
-        super.onCreate()
-        DynamicColors.applyToActivitiesIfAvailable(this)
+class RequestManager {
+
+    @GuardedBy("this")
+    private val contMap = ConcurrentHashMap<Int, Continuation<ApiResponse>>()
+
+    @Volatile
+    @GuardedBy("this")
+    private var currentId = 0
+
+    @Synchronized
+    fun addRequest(cont: Continuation<ApiResponse>, block: (Int) -> Unit) {
+        val id = currentId++
+        contMap[id] = cont
+        block(id)
     }
+
+    @Synchronized
+    fun getAndRemoveContinuation(id: Int): Continuation<ApiResponse>? {
+        return contMap.remove(id)
+    }
+
 }
diff --git a/wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt 
b/wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt
index edcfd17..2242e33 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/TalerErrorCode.kt
@@ -3879,9 +3879,10 @@ enum class TalerErrorCode(val code: Int) {
 
 @OptIn(ExperimentalSerializationApi::class)
 @Serializer(forClass = TalerErrorCode::class)
-object TalerErrorCodeSerializer: KSerializer<TalerErrorCode> {
+object TalerErrorCodeSerializer : KSerializer<TalerErrorCode> {
 
-    override val descriptor = 
PrimitiveSerialDescriptor("TalerErrorCodeSerializer", PrimitiveKind.INT)
+    override val descriptor =
+        PrimitiveSerialDescriptor("TalerErrorCodeSerializer", 
PrimitiveKind.INT)
 
     override fun deserialize(decoder: Decoder): TalerErrorCode {
         val code = decoder.decodeInt()
diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
index 8ec5873..06b8cee 100644
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
+++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt
@@ -17,130 +17,48 @@
 package net.taler.wallet.backend
 
 import android.app.Application
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
-import android.content.ServiceConnection
-import android.os.Handler
-import android.os.IBinder
-import android.os.Message
-import android.os.Messenger
-import android.util.Log
+import kotlinx.coroutines.DelicateCoroutinesApi
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import kotlinx.serialization.KSerializer
-import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.decodeFromJsonElement
 import net.taler.wallet.backend.TalerErrorCode.NONE
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_COMMAND
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_NOTIFY
-import net.taler.wallet.backend.WalletBackendService.Companion.MSG_REPLY
-import 
net.taler.wallet.backend.WalletBackendService.Companion.MSG_SUBSCRIBE_NOTIFY
 import org.json.JSONObject
-import java.lang.ref.WeakReference
-import java.util.LinkedList
-import java.util.concurrent.ConcurrentHashMap
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
 
+const val WALLET_DB = "talerwalletdb-v30.json"
+
+@OptIn(DelicateCoroutinesApi::class)
 class WalletBackendApi(
-    private val app: Application,
-    private val notificationHandler: ((payload: JSONObject) -> Unit),
+    app: Application,
+    private val versionReceiver: VersionReceiver,
+    notificationReceiver: NotificationReceiver,
 ) {
-    private var walletBackendMessenger: Messenger? = null
-    private val queuedMessages = LinkedList<Message>()
-    private val handlers = ConcurrentHashMap<Int, (isError: Boolean, message: 
JSONObject) -> Unit>()
-    private var nextRequestID = AtomicInteger(0)
-    private val incomingMessenger = Messenger(IncomingHandler(this))
-
-    private val walletBackendConn = object : ServiceConnection {
-        override fun onServiceDisconnected(p0: ComponentName?) {
-            Log.w(TAG, "wallet backend service disconnected (crash?)")
-            walletBackendMessenger = null
-        }
 
-        override fun onServiceConnected(componentName: ComponentName?, binder: 
IBinder?) {
-            Log.i(TAG, "connected to wallet backend service")
-            val bm = Messenger(binder)
-            walletBackendMessenger = bm
-            pumpQueue(bm)
-            val msg = Message.obtain(null, MSG_SUBSCRIBE_NOTIFY)
-            msg.replyTo = incomingMessenger
-            bm.send(msg)
-        }
-    }
+    private val backendManager = BackendManager(notificationReceiver)
+    private val dbPath = "${app.filesDir}/${WALLET_DB}"
 
     init {
-        Intent(app, WalletBackendService::class.java).also { intent ->
-            app.bindService(intent, walletBackendConn, 
Context.BIND_AUTO_CREATE)
-        }
-    }
-
-    private class IncomingHandler(strongApi: WalletBackendApi) : Handler() {
-        private val weakApi = WeakReference(strongApi)
-        override fun handleMessage(msg: Message) {
-            val api = weakApi.get() ?: return
-            when (msg.what) {
-                MSG_REPLY -> {
-                    val requestID = msg.data.getInt("requestID", 0)
-                    val operation = msg.data.getString("operation", "??")
-                    Log.i(TAG, "got reply for operation $operation 
($requestID)")
-                    val h = api.handlers.remove(requestID)
-                    if (h == null) {
-                        Log.e(TAG, "request ID not associated with a handler")
-                        return
-                    }
-                    val response = msg.data.getString("response")
-                    if (response == null) {
-                        Log.e(TAG, "response did not contain response payload")
-                        return
-                    }
-                    val isError = msg.data.getBoolean("isError")
-                    val json = JSONObject(response)
-                    h(isError, json)
-                }
-                MSG_NOTIFY -> {
-                    val payloadStr = msg.data.getString("payload")
-                    if (payloadStr == null) {
-                        Log.e(TAG, "Notification had no payload: $msg")
-                    } else {
-                        val payload = JSONObject(payloadStr)
-                        api.notificationHandler.invoke(payload)
-                    }
-                }
-            }
+        GlobalScope.launch(Dispatchers.IO) {
+            backendManager.run()
+            sendInitMessage()
         }
     }
 
-    private fun pumpQueue(bm: Messenger) {
-        while (true) {
-            val msg = queuedMessages.pollFirst() ?: return
-            bm.send(msg)
+    private suspend fun sendInitMessage() {
+        request("init", InitResponse.serializer()) {
+            put("persistentStoragePath", dbPath)
+            put("logLevel", "INFO")
+        }.onSuccess { response ->
+            versionReceiver.onVersionReceived(response.versionInfo)
+        }.onError { error ->
+            error("Error on init message: $error")
         }
     }
 
-    fun sendRequest(
-        operation: String,
-        args: JSONObject? = null,
-        onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ 
-> },
-    ) {
-        val requestID = nextRequestID.incrementAndGet()
-        Log.i(TAG, "sending request for operation $operation 
($requestID)\n${args?.toString(2)}")
-        val msg = Message.obtain(null, MSG_COMMAND)
-        handlers[requestID] = onResponse
-        msg.replyTo = incomingMessenger
-        val data = msg.data
-        data.putString("operation", operation)
-        data.putInt("requestID", requestID)
-        if (args != null) {
-            data.putString("args", args.toString())
-        }
-        val bm = walletBackendMessenger
-        if (bm != null) {
-            bm.send(msg)
-        } else {
-            queuedMessages.add(msg)
-        }
+    suspend fun sendRequest(operation: String, args: JSONObject? = null): 
ApiResponse {
+        return backendManager.send(operation, args)
     }
 
     suspend inline fun <reified T> request(
@@ -148,36 +66,23 @@ class WalletBackendApi(
         serializer: KSerializer<T>? = null,
         noinline args: (JSONObject.() -> JSONObject)? = null,
     ): WalletResponse<T> = withContext(Dispatchers.Default) {
-        suspendCoroutine { cont ->
-            val json = Json {
-                ignoreUnknownKeys = true
-            }
-            sendRequest(operation, args?.invoke(JSONObject())) { isError, 
message ->
-                val response = try {
-                    if (isError) {
-                        val error =
-                            json.decodeFromString(TalerErrorInfo.serializer(), 
message.toString())
-                        WalletResponse.Error(error)
-                    } else {
-                        val t: T = serializer?.let {
-                            json.decodeFromString(serializer, 
message.toString())
-                        } ?: Unit as T
-                        WalletResponse.Success(t)
-                    }
-                } catch (e: Exception) {
-                    val info = TalerErrorInfo(NONE, "", e.toString())
-                    WalletResponse.Error(info)
+        val json = BackendManager.json
+        try {
+            when (val response = sendRequest(operation, 
args?.invoke(JSONObject()))) {
+                is ApiResponse.Response -> {
+                    val t: T = serializer?.let {
+                        json.decodeFromJsonElement(serializer, response.result)
+                    } ?: Unit as T
+                    WalletResponse.Success(t)
+                }
+                is ApiResponse.Error -> {
+                    val error: TalerErrorInfo = 
json.decodeFromJsonElement(response.error)
+                    WalletResponse.Error(error)
                 }
-                cont.resume(response)
             }
+        } catch (e: Exception) {
+            val info = TalerErrorInfo(NONE, "", e.toString())
+            WalletResponse.Error(info)
         }
     }
-
-    fun destroy() {
-        // FIXME: implement this!
-    }
-
-    companion object {
-        const val TAG = "WalletBackendApi"
-    }
 }
diff --git 
a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt 
b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
deleted file mode 100644
index 6411b8b..0000000
--- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendService.kt
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under 
the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
- * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-
-package net.taler.wallet.backend
-
-import android.app.Service
-import android.content.Intent
-import android.os.Handler
-import android.os.IBinder
-import android.os.Message
-import android.os.Messenger
-import android.os.RemoteException
-import android.util.Log
-import net.taler.qtart.TalerWalletCore
-import net.taler.wallet.BuildConfig
-import net.taler.wallet.HostCardEmulatorService
-import org.json.JSONObject
-import java.lang.ref.WeakReference
-import java.util.LinkedList
-import java.util.concurrent.ConcurrentHashMap
-import kotlin.system.exitProcess
-
-private const val TAG = "taler-wallet-backend"
-const val WALLET_DB = "talerwalletdb-v30.json"
-
-class RequestData(val clientRequestId: Int, val messenger: Messenger)
-
-
-class WalletBackendService : Service() {
-    /**
-     * Target we publish for clients to send messages to IncomingHandler.
-     */
-    private val messenger: Messenger = Messenger(IncomingHandler(this))
-
-    private val walletCore = TalerWalletCore()
-
-    private var initialized = false
-
-    private var nextRequestID = 1
-
-    private val requests = ConcurrentHashMap<Int, RequestData>()
-
-    private val subscribers = LinkedList<Messenger>()
-
-    override fun onCreate() {
-        Log.i(TAG, "onCreate in wallet backend service")
-
-        walletCore.setMessageHandler {
-            this@WalletBackendService.handleAkonoMessage(it)
-        }
-        if (BuildConfig.DEBUG) walletCore.setStdoutHandler {
-            Log.d(TAG, it)
-        }
-        walletCore.run()
-        sendInitMessage()
-        // runIntegrationTest()
-        super.onCreate()
-    }
-
-    private fun sendInitMessage() {
-        val msg = JSONObject()
-        msg.put("operation", "init")
-        val args = JSONObject()
-        msg.put("args", args)
-        args.put("persistentStoragePath", "${application.filesDir}/$WALLET_DB")
-        args.put("logLevel", "INFO")
-        Log.d(TAG, "init message: ${msg.toString(2)}")
-        walletCore.sendRequest(msg.toString())
-    }
-
-    /**
-     * Run the integration tests for wallet-core.
-     */
-    private fun runIntegrationTest() {
-        val msg = JSONObject()
-        msg.put("operation", "runIntegrationTest")
-        val args = JSONObject()
-        msg.put("args", args)
-        args.put("amountToWithdraw", "KUDOS:3")
-        args.put("amountToSpend", "KUDOS:1")
-        args.put("bankBaseUrl", 
"https://bank.demo.taler.net/demobanks/default/access-api/";)
-        args.put("exchangeBaseUrl", "https://exchange.demo.taler.net/";)
-        args.put("merchantBaseUrl", "https://backend.demo.taler.net/";)
-        args.put("merchantAuthToken", "secret-token:sandbox")
-        Log.d(TAG, "integration test message: ${msg.toString(2)}")
-        walletCore.sendRequest(msg.toString())
-    }
-
-    /**
-     * Handler of incoming messages from clients.
-     */
-    class IncomingHandler(
-        service: WalletBackendService,
-    ) : Handler() {
-
-        private val serviceWeakRef = WeakReference(service)
-
-        override fun handleMessage(msg: Message) {
-            val svc = serviceWeakRef.get() ?: return
-            if (!svc.initialized) Log.w(TAG, "Warning: Not yet initialized")
-            when (msg.what) {
-                MSG_COMMAND -> {
-                    val data = msg.data
-                    val serviceRequestID = svc.nextRequestID++
-                    val clientRequestID = data.getInt("requestID", 0)
-                    if (clientRequestID == 0) {
-                        Log.e(TAG, "client requestID missing")
-                        return
-                    }
-                    val args = data.getString("args")
-                    val argsObj = if (args == null) {
-                        JSONObject()
-                    } else {
-                        JSONObject(args)
-                    }
-                    val operation = data.getString("operation", "")
-                    if (operation == "") {
-                        Log.e(TAG, "client command missing")
-                        return
-                    }
-                    Log.i(TAG, "got request for operation $operation")
-                    val request = JSONObject()
-                    request.put("operation", operation)
-                    request.put("id", serviceRequestID)
-                    request.put("args", argsObj)
-                    svc.walletCore.sendRequest(request.toString())
-                    Log.i(
-                        TAG,
-                        "mapping service request ID $serviceRequestID to 
client request ID $clientRequestID"
-                    )
-                    svc.requests[serviceRequestID] = 
RequestData(clientRequestID, msg.replyTo)
-                }
-                MSG_SUBSCRIBE_NOTIFY -> {
-                    Log.i(TAG, "subscribing client")
-                    val r = msg.replyTo
-                    if (r == null) {
-                        Log.e(
-                            TAG,
-                            "subscriber did not specify replyTo object in 
MSG_SUBSCRIBE_NOTIFY"
-                        )
-                    } else {
-                        svc.subscribers.add(msg.replyTo)
-                    }
-                }
-                MSG_UNSUBSCRIBE_NOTIFY -> {
-                    Log.i(TAG, "unsubscribing client")
-                    svc.subscribers.remove(msg.replyTo)
-                }
-                else -> {
-                    Log.e(TAG, "unknown message from client")
-                    super.handleMessage(msg)
-                }
-            }
-        }
-    }
-
-    override fun onBind(p0: Intent?): IBinder? {
-        return messenger.binder
-    }
-
-    private fun sendNotify(payload: String) {
-        var rm: LinkedList<Messenger>? = null
-        for (s in subscribers) {
-            val m = Message.obtain(null, MSG_NOTIFY)
-            val b = m.data
-            b.putString("payload", payload)
-            try {
-                s.send(m)
-            } catch (e: RemoteException) {
-                if (rm == null) {
-                    rm = LinkedList()
-                }
-                rm.add(s)
-                subscribers.remove(s)
-            }
-        }
-        if (rm != null) {
-            for (s in rm) {
-                subscribers.remove(s)
-            }
-        }
-    }
-
-    private fun handleAkonoMessage(messageStr: String) {
-        val message = JSONObject(messageStr)
-        when (val type = message.getString("type")) {
-            "notification" -> {
-                val payload = message.getJSONObject("payload")
-                if (payload.optString("type") != "waiting-for-retry") {
-                    Log.v(TAG, "got back notification: ${message.toString(2)}")
-                }
-                sendNotify(payload.toString())
-            }
-            "tunnelHttp" -> {
-                Log.v(TAG, "got http tunnel request! ${message.toString(2)}")
-                Intent().also { intent ->
-                    intent.action = HostCardEmulatorService.HTTP_TUNNEL_REQUEST
-                    intent.putExtra("tunnelMessage", messageStr)
-                    application.sendBroadcast(intent)
-                }
-            }
-            "response" -> {
-                when (message.getString("operation")) {
-                    "init" -> {
-                        Log.d(TAG, "got response for init operation: 
${message.toString(2)}")
-                        initialized = true
-                        sendNotify(message.toString(2))
-                    }
-                    "reset" -> {
-                        Log.v(TAG, "got back message: ${message.toString(2)}")
-                        exitProcess(1)
-                    }
-                    else -> {
-                        Log.v(TAG, "got back response: ${message.toString(2)}")
-                        val payload = 
message.getJSONObject("result").toString(2)
-                        handleResponse(false, message, payload)
-                    }
-                }
-            }
-            "error" -> {
-                Log.v(TAG, "got back error: ${message.toString(2)}")
-                val payload = message.getJSONObject("error").toString(2)
-                handleResponse(true, message, payload)
-            }
-            else -> throw IllegalArgumentException("Unknown message type: 
$type")
-        }
-    }
-
-    private fun handleResponse(isError: Boolean, message: JSONObject, payload: 
String) {
-        val id = message.getInt("id")
-        val rId = requests[id]
-        if (rId == null) {
-            Log.e(TAG, "wallet returned unknown request ID ($id)")
-            return
-        }
-        val m = Message.obtain(null, MSG_REPLY)
-        val b = m.data
-        b.putInt("requestID", rId.clientRequestId)
-        b.putBoolean("isError", isError)
-        b.putString("response", payload)
-        b.putString("operation", message.getString("operation"))
-        rId.messenger.send(m)
-    }
-
-    companion object {
-        const val MSG_SUBSCRIBE_NOTIFY = 1
-        const val MSG_UNSUBSCRIBE_NOTIFY = 2
-        const val MSG_COMMAND = 3
-        const val MSG_REPLY = 4
-        const val MSG_NOTIFY = 5
-    }
-}
diff --git 
a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt 
b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
index df778ed..f5079f6 100644
--- a/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
+++ b/wallet/src/main/java/net/taler/wallet/pending/PendingOperationsManager.kt
@@ -18,40 +18,51 @@ package net.taler.wallet.pending
 
 import android.util.Log
 import androidx.lifecycle.MutableLiveData
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.serialization.json.jsonArray
 import net.taler.wallet.TAG
+import net.taler.wallet.backend.ApiResponse
 import net.taler.wallet.backend.WalletBackendApi
 import org.json.JSONObject
 
 open class PendingOperationInfo(
     val type: String,
-    val detail: JSONObject
+    val detail: JSONObject,
 )
 
-class PendingOperationsManager(private val walletBackendApi: WalletBackendApi) 
{
+class PendingOperationsManager(
+    private val walletBackendApi: WalletBackendApi,
+    private val scope: CoroutineScope,
+) {
 
     val pendingOperations = MutableLiveData<List<PendingOperationInfo>>()
 
     internal fun getPending() {
-        walletBackendApi.sendRequest("getPendingOperations") { isError, result 
->
-            if (isError) {
-                Log.i(TAG, "got getPending error result: 
${result.toString(2)}")
-                return@sendRequest
+        scope.launch {
+            val response = walletBackendApi.sendRequest("getPendingOperations")
+            if (response is ApiResponse.Error) {
+                Log.i(TAG, "got getPending error result: ${response.error}")
+                return@launch
+            } else if (response is ApiResponse.Response) {
+                Log.i(TAG, "got getPending result")
+                val pendingList = mutableListOf<PendingOperationInfo>()
+                val pendingJson = 
response.result["pendingOperations"]?.jsonArray ?: return@launch
+                for (i in 0 until pendingJson.size) {
+                    val p = JSONObject(pendingJson[i].toString())
+                    val type = p.getString("type")
+                    pendingList.add(PendingOperationInfo(type, p))
+                }
+                Log.i(TAG, "Got ${pendingList.size} pending operations")
+                pendingOperations.postValue((pendingList))
             }
-            Log.i(TAG, "got getPending result")
-            val pendingList = mutableListOf<PendingOperationInfo>()
-            val pendingJson = result.getJSONArray("pendingOperations")
-            for (i in 0 until pendingJson.length()) {
-                val p = pendingJson.getJSONObject(i)
-                val type = p.getString("type")
-                pendingList.add(PendingOperationInfo(type, p))
-            }
-            Log.i(TAG, "Got ${pendingList.size} pending operations")
-            pendingOperations.postValue((pendingList))
         }
     }
 
     fun retryPendingNow() {
-        walletBackendApi.sendRequest("retryPendingNow")
+        scope.launch {
+            walletBackendApi.sendRequest("retryPendingNow")
+        }
     }
 
 }

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]