diff options
author | Torsten Grote <t@grobox.de> | 2020-08-11 15:24:25 -0300 |
---|---|---|
committer | Torsten Grote <t@grobox.de> | 2020-08-11 16:08:56 -0300 |
commit | d13be7c5c1be2492d38959a29e1b1c33df4938ff (patch) | |
tree | 3e4ae91e98c0388289779b3f8d972df196aa6a49 /wallet/src/main/java/net/taler/wallet/backend | |
parent | f0670e2f3936f0223c02e9ec0d0de52f31a3539f (diff) | |
download | taler-android-d13be7c5c1be2492d38959a29e1b1c33df4938ff.tar.gz taler-android-d13be7c5c1be2492d38959a29e1b1c33df4938ff.tar.bz2 taler-android-d13be7c5c1be2492d38959a29e1b1c33df4938ff.zip |
[wallet] start to move deserialization into the backend API
and off the UI thread for less sluggish, i.e. more responsive UI
Diffstat (limited to 'wallet/src/main/java/net/taler/wallet/backend')
-rw-r--r-- | wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt | 75 | ||||
-rw-r--r-- | wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt | 82 |
2 files changed, 134 insertions, 23 deletions
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 51b3419..ea8f26f 100644 --- a/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt +++ b/wallet/src/main/java/net/taler/wallet/backend/WalletBackendApi.kt @@ -14,7 +14,6 @@ * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - package net.taler.wallet.backend import android.app.Application @@ -27,21 +26,35 @@ import android.os.IBinder import android.os.Message import android.os.Messenger import android.util.Log -import android.util.SparseArray +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration +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 class WalletBackendApi( private val app: Application, - private val onConnected: (() -> Unit), private val notificationHandler: ((payload: JSONObject) -> Unit) ) { - + private val json = Json( + JsonConfiguration.Stable.copy(ignoreUnknownKeys = true) + ) private var walletBackendMessenger: Messenger? = null private val queuedMessages = LinkedList<Message>() - private val handlers = SparseArray<(isError: Boolean, message: JSONObject) -> Unit>() - private var nextRequestID = 1 + 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?) { @@ -54,10 +67,15 @@ class WalletBackendApi( val bm = Messenger(binder) walletBackendMessenger = bm pumpQueue(bm) - val msg = Message.obtain(null, WalletBackendService.MSG_SUBSCRIBE_NOTIFY) + val msg = Message.obtain(null, MSG_SUBSCRIBE_NOTIFY) msg.replyTo = incomingMessenger bm.send(msg) - onConnected.invoke() + } + } + + init { + Intent(app, WalletBackendService::class.java).also { intent -> + app.bindService(intent, walletBackendConn, Context.BIND_AUTO_CREATE) } } @@ -66,11 +84,11 @@ class WalletBackendApi( override fun handleMessage(msg: Message) { val api = weakApi.get() ?: return when (msg.what) { - WalletBackendService.MSG_REPLY -> { + 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.get(requestID) + val h = api.handlers.remove(requestID) if (h == null) { Log.e(TAG, "request ID not associated with a handler") return @@ -84,7 +102,7 @@ class WalletBackendApi( val json = JSONObject(response) h(isError, json) } - WalletBackendService.MSG_NOTIFY -> { + MSG_NOTIFY -> { val payloadStr = msg.data.getString("payload") if (payloadStr == null) { Log.e(TAG, "Notification had no payload: $msg") @@ -97,14 +115,6 @@ class WalletBackendApi( } } - private val incomingMessenger = Messenger(IncomingHandler(this)) - - init { - Intent(app, WalletBackendService::class.java).also { intent -> - app.bindService(intent, walletBackendConn, Context.BIND_AUTO_CREATE) - } - } - private fun pumpQueue(bm: Messenger) { while (true) { val msg = queuedMessages.pollFirst() ?: return @@ -112,16 +122,15 @@ class WalletBackendApi( } } - fun sendRequest( operation: String, args: JSONObject? = null, onResponse: (isError: Boolean, message: JSONObject) -> Unit = { _, _ -> } ) { - val requestID = nextRequestID++ + val requestID = nextRequestID.incrementAndGet() Log.i(TAG, "sending request for operation $operation ($requestID)") - val msg = Message.obtain(null, WalletBackendService.MSG_COMMAND) - handlers.put(requestID, onResponse) + val msg = Message.obtain(null, MSG_COMMAND) + handlers[requestID] = onResponse msg.replyTo = incomingMessenger val data = msg.data data.putString("operation", operation) @@ -137,6 +146,26 @@ class WalletBackendApi( } } + suspend fun <T> request( + operation: String, + serializer: KSerializer<T>? = null, + args: (JSONObject.() -> JSONObject)? = null + ): WalletResponse<T> = withContext(Dispatchers.Default) { + suspendCoroutine<WalletResponse<T>> { cont -> + sendRequest(operation, args?.invoke(JSONObject())) { isError, message -> + val response = if (isError) { + val error = json.parse(WalletErrorInfo.serializer(), message.toString()) + WalletResponse.Error<T>(error) + } else { + @Suppress("UNCHECKED_CAST") // if serializer is null, T must be Unit + val t: T = serializer?.let { json.parse(serializer, message.toString()) } ?: Unit as T + WalletResponse.Success(t) + } + cont.resume(response) + } + } + } + fun destroy() { // FIXME: implement this! } diff --git a/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt new file mode 100644 index 0000000..05a53f3 --- /dev/null +++ b/wallet/src/main/java/net/taler/wallet/backend/WalletResponse.kt @@ -0,0 +1,82 @@ +/* + * 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 kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.json.JSONObject + +@Serializable +sealed class WalletResponse<T> { + @Serializable + @SerialName("response") + data class Success<T>( + val result: T + ) : WalletResponse<T>() + + @Serializable + @SerialName("error") + data class Error<T>( + val error: WalletErrorInfo + ) : WalletResponse<T>() + + fun onSuccess(block: (result: T) -> Unit): WalletResponse<T> { + if (this is Success) block(this.result) + return this + } + + fun onError(block: (result: WalletErrorInfo) -> Unit): WalletResponse<T> { + if (this is Error) block(this.error) + return this + } +} + +@Serializable +data class WalletErrorInfo( + // Numeric error code defined defined in the + // GANA gnu-taler-error-codes registry. + val talerErrorCode: Int, + + // English description of the error code. + val talerErrorHint: String, + + // English diagnostic message that can give details + // for the instance of the error. + val message: String, + + // Error details, type depends + // on talerErrorCode + val details: String? +) { + val userFacingMsg: String + get() { + return StringBuilder().apply { + append(talerErrorCode) + append(" ") + append(message) + details?.let { it -> + val details = JSONObject(it) + details.optJSONObject("errorResponse")?.let { errorResponse -> + append("\n\n") + append(errorResponse.optString("code")) + append(" ") + append(errorResponse.optString("hint")) + } + } + }.toString() + } +} |