taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit 1da8cdbd06e2c885647ab8da7a0ea9a4b475935f
parent 67c5f6813ae54148622f522202612557fa82b2f2
Author: Iván Ávalos <avalos@disroot.org>
Date:   Wed, 12 Aug 2026 10:18:11 +0200

[wallet] timeout wallet-core requests and wait non-blockingly

Diffstat:
Mwallet/src/main/java/net/taler/wallet/backend/BackendManager.kt | 41+++++++++++++++++++++++++++++++----------
Mwallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt | 27+++++++++++++++------------
Mwallet/src/main/java/net/taler/wallet/donau/DonauManager.kt | 5++---
3 files changed, 48 insertions(+), 25 deletions(-)

diff --git a/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt b/wallet/src/main/java/net/taler/wallet/backend/BackendManager.kt @@ -22,13 +22,16 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject import net.taler.qtart.TalerWalletCore import net.taler.wallet.BuildConfig import org.json.JSONObject import java.util.concurrent.atomic.AtomicBoolean import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine fun interface NotificationReceiver { @@ -42,6 +45,7 @@ class BackendManager( companion object { private const val TAG = "BackendManager" private const val TAG_CORE = "taler-wallet-embedded" + private const val REQUEST_TIMEOUT_MS = 60_000L val json = Json { ignoreUnknownKeys = true coerceInputValues = true @@ -73,18 +77,35 @@ class BackendManager( } } - 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) + suspend fun send(operation: String, args: JSONObject? = null): ApiResponse { + var requestId = -1 + val response = withTimeoutOrNull(REQUEST_TIMEOUT_MS) { + suspendCancellableCoroutine { cont -> + requestManager.addRequest(cont) { id -> + requestId = 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()) + } + cont.invokeOnCancellation { + requestManager.getAndRemoveContinuation(requestId) } - Log.d(TAG, "sending message:\n${request.toString(2)}") - walletCore.sendRequest(request.toString()) } } + if (response != null) return response + return ApiResponse.Error( + id = requestId, + operation = operation, + error = buildJsonObject { + put("hint", JsonPrimitive("wallet-core did not respond")) + put("message", JsonPrimitive("request '$operation' timed out")) + }, + ) + } private fun onMessageReceived(msg: String) = scope.launch { Log.d(TAG, "message received: $msg") diff --git a/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt b/wallet/src/main/java/net/taler/wallet/balances/BalanceManager.kt @@ -23,13 +23,13 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.distinctUntilChanged import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.serialization.Serializable import net.taler.common.Amount import net.taler.common.CurrencySpecification import net.taler.wallet.main.TAG import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.WalletBackendApi +import net.taler.wallet.backend.WalletResponse import net.taler.wallet.donau.DonauSummaryItem import net.taler.wallet.exchanges.ExchangeItem import net.taler.wallet.exchanges.ExchangeManager @@ -78,14 +78,16 @@ class BalanceManager( fun loadAssets(loading: Boolean = false) = scope.launch { if (loading) mState.postValue(BalanceState.Loading) - api.request("getBalances", BalanceResponse.serializer()) - .onError { - Log.e(TAG, "Error retrieving balances: $it") - mState.postValue(BalanceState.Error(it)) - }.onSuccess { res -> - val balances = res.balances.map { balance -> - val spec = runBlocking { exchangeManager - .getCurrencySpecification(balance.scopeInfo) } + when (val response = api.request("getBalances", BalanceResponse.serializer())) { + is WalletResponse.Error -> { + Log.e(TAG, "Error retrieving balances: ${response.error}") + mState.postValue(BalanceState.Error(response.error)) + } + + is WalletResponse.Success -> { + val balances = response.result.balances.map { balance -> + val spec = exchangeManager + .getCurrencySpecification(balance.scopeInfo) balance.copy( available = balance.available.withSpec(spec), pendingIncoming = balance.pendingIncoming.withSpec(spec), @@ -93,9 +95,9 @@ class BalanceManager( ) } - val donauSummary = res.donauSummary?.map { item -> - val spec = runBlocking { exchangeManager - .getSpecForCurrency(item.amountReceiptsAvailable.currency) } + val donauSummary = response.result.donauSummary?.map { item -> + val spec = exchangeManager + .getSpecForCurrency(item.amountReceiptsAvailable.currency) item.copy( amountReceiptsAvailable = item.amountReceiptsAvailable.withSpec(spec), amountReceiptsSubmitted = item.amountReceiptsSubmitted.withSpec(spec), @@ -109,6 +111,7 @@ class BalanceManager( donauSummary = donauSummary, )) } + } } @UiThread diff --git a/wallet/src/main/java/net/taler/wallet/donau/DonauManager.kt b/wallet/src/main/java/net/taler/wallet/donau/DonauManager.kt @@ -21,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import net.taler.wallet.main.TAG import net.taler.wallet.backend.TalerErrorInfo import net.taler.wallet.backend.WalletBackendApi @@ -79,8 +78,8 @@ class DonauManager( mDonauStatementsStatus.value = GetDonauStatementsStatus.Error(error) }.onSuccess { res -> val statements = res.statements.map { statement -> - val spec = runBlocking { exchangeManager - .getSpecForCurrency(statement.total.currency) } + val spec = exchangeManager + .getSpecForCurrency(statement.total.currency) statement.copy( total = statement.total.withSpec(spec) )