taler-android

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

SettingsManager.kt (10157B)


      1 /*
      2  * This file is part of GNU Taler
      3  * (C) 2022 Taler Systems S.A.
      4  *
      5  * GNU Taler is free software; you can redistribute it and/or modify it under the
      6  * terms of the GNU General Public License as published by the Free Software
      7  * Foundation; either version 3, or (at your option) any later version.
      8  *
      9  * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12  *
     13  * You should have received a copy of the GNU General Public License along with
     14  * GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 package net.taler.wallet.settings
     18 
     19 import android.content.Context
     20 import android.net.Uri
     21 import android.util.Log
     22 import android.widget.Toast
     23 import android.widget.Toast.LENGTH_LONG
     24 import kotlinx.coroutines.CoroutineScope
     25 import kotlinx.coroutines.Dispatchers
     26 import kotlinx.coroutines.flow.MutableStateFlow
     27 import kotlinx.coroutines.flow.StateFlow
     28 import kotlinx.coroutines.flow.map
     29 import kotlinx.coroutines.launch
     30 import kotlinx.coroutines.withContext
     31 import kotlinx.serialization.json.Json
     32 import kotlinx.serialization.json.JsonObject
     33 import net.taler.wallet.R
     34 import net.taler.wallet.backend.TalerErrorInfo
     35 import net.taler.wallet.main.ViewMode
     36 import net.taler.wallet.backend.WalletBackendApi
     37 import net.taler.wallet.backend.WalletResponse.Error
     38 import net.taler.wallet.backend.WalletResponse.Success
     39 import net.taler.wallet.balances.BalanceManager
     40 import net.taler.wallet.main.TAG
     41 import org.json.JSONObject
     42 
     43 class SettingsManager(
     44     private val context: Context,
     45     private val api: WalletBackendApi,
     46     private val scope: CoroutineScope,
     47     private val balanceManager: BalanceManager,
     48 ) {
     49     private val mPerformanceTable = MutableStateFlow<PerformanceTable?>(null)
     50     val performanceTable: StateFlow<PerformanceTable?> = mPerformanceTable
     51 
     52     fun getViewMode(c: Context) = c.userPreferencesDataStore.data.map { prefs ->
     53         if (prefs.hasViewMode()) {
     54             ViewMode.fromPrefs(prefs.viewMode)
     55         } else {
     56             null
     57         }
     58     }
     59 
     60     fun saveViewMode(c: Context, viewMode: ViewMode?) = scope.launch {
     61         c.userPreferencesDataStore.updateData { current ->
     62             if (viewMode != null) {
     63                 current.toBuilder()
     64                     .setViewMode(viewMode.toPrefs())
     65                     .build()
     66             } else {
     67                 current.toBuilder()
     68                     .clearViewMode()
     69                     .build()
     70             }
     71         }
     72     }
     73 
     74     fun getActionButtonUsed(c: Context) = c.userPreferencesDataStore.data.map { prefs ->
     75         if (prefs.hasActionButtonUsed()) {
     76             prefs.actionButtonUsed
     77         } else {
     78             false
     79         }
     80     }
     81 
     82     fun saveActionButtonUsed(c: Context) = scope.launch {
     83         c.userPreferencesDataStore.updateData { current ->
     84             current.toBuilder()
     85                 .setActionButtonUsed(true)
     86                 .build()
     87         }
     88     }
     89 
     90     fun getDevModeEnabled(c: Context) = c.userPreferencesDataStore.data.map { prefs ->
     91         if (prefs.hasDevModeEnabled()) {
     92             prefs.devModeEnabled
     93         } else {
     94             false
     95         }
     96     }
     97 
     98     fun setDevModeEnabled(c: Context, enabled: Boolean) = scope.launch {
     99         c.userPreferencesDataStore.updateData { current ->
    100             current.toBuilder()
    101                 .setDevModeEnabled(enabled)
    102                 .build()
    103         }
    104     }
    105 
    106     fun getBiometricLockEnabled(c: Context) = c.userPreferencesDataStore.data.map { prefs ->
    107         if (prefs.hasBiometricLockEnabled()) {
    108             prefs.biometricLockEnabled
    109         } else {
    110             false
    111         }
    112     }
    113 
    114     fun setBiometricLockEnabled(c: Context, enabled: Boolean) = scope.launch {
    115         c.userPreferencesDataStore.updateData { current ->
    116             current.toBuilder()
    117                 .setBiometricLockEnabled(enabled)
    118                 .build()
    119         }
    120     }
    121 
    122     fun exportLogcat(uri: Uri?) {
    123         if (uri == null) {
    124             onLogExportError()
    125             return
    126         }
    127         scope.launch(Dispatchers.IO) {
    128             try {
    129                 context.contentResolver.openOutputStream(uri, "wt")?.use { outputStream ->
    130                     val command = arrayOf("logcat", "-d", "*:V")
    131                     val proc = Runtime.getRuntime().exec(command)
    132                     proc.inputStream.copyTo(outputStream)
    133                 } ?: onLogExportError()
    134             } catch (e: Exception) {
    135                 Log.e(SettingsManager::class.simpleName, "Error exporting log: ", e)
    136                 onLogExportError()
    137                 return@launch
    138             }
    139             withContext(Dispatchers.Main) {
    140                 Toast.makeText(context, R.string.settings_logcat_success, LENGTH_LONG).show()
    141             }
    142         }
    143     }
    144 
    145     private fun onLogExportError() {
    146         Toast.makeText(context, R.string.settings_logcat_error, LENGTH_LONG).show()
    147     }
    148 
    149     fun exportDb(uri: Uri?) {
    150         if (uri == null) {
    151             onDbExportError()
    152             return
    153         }
    154 
    155         scope.launch(Dispatchers.IO) {
    156             when (val response = api.rawRequest("exportDb")) {
    157                 is Success -> {
    158                     try {
    159                         context.contentResolver.openOutputStream(uri, "wt")?.use { outputStream ->
    160                             val data = Json.encodeToString(response.result)
    161                             val writer = outputStream.bufferedWriter()
    162                             writer.write(data)
    163                             writer.close()
    164                         }
    165                     } catch(e: Exception) {
    166                         Log.e(SettingsManager::class.simpleName, "Error exporting db: ", e)
    167                         withContext(Dispatchers.Main) {
    168                             onDbExportError()
    169                         }
    170                         return@launch
    171                     }
    172 
    173                     withContext(Dispatchers.Main) {
    174                         Toast.makeText(context, R.string.settings_db_export_success, LENGTH_LONG).show()
    175                     }
    176                 }
    177                 is Error -> {
    178                     Log.e(SettingsManager::class.simpleName, "Error exporting db: ${response.error}")
    179                     withContext(Dispatchers.Main) {
    180                         onDbExportError()
    181                     }
    182                     return@launch
    183                 }
    184             }
    185         }
    186     }
    187 
    188     fun importDb(uri: Uri?) {
    189         if (uri == null) {
    190             onDbImportError()
    191             return
    192         }
    193 
    194         scope.launch(Dispatchers.IO) {
    195             context.contentResolver.openInputStream(uri)?.use {  inputStream ->
    196                 try {
    197                     val reader = inputStream.bufferedReader()
    198                     val strData = reader.readText()
    199                     reader.close()
    200                     val jsonData = JSONObject(strData)
    201                     when (val response = api.rawRequest("importDb") {
    202                         put("dump", jsonData)
    203                     }) {
    204                         is Success -> {
    205                             withContext(Dispatchers.Main) {
    206                                 Toast.makeText(context, R.string.settings_db_import_success, LENGTH_LONG).show()
    207                                 balanceManager.loadAssets(true)
    208                             }
    209                         }
    210                         is Error -> {
    211                             Log.e(SettingsManager::class.simpleName, "Error importing db: ${response.error}")
    212                             withContext(Dispatchers.Main) {
    213                                 onDbImportError()
    214                             }
    215                             return@launch
    216                         }
    217                     }
    218                 } catch (e: Exception) {
    219                     Log.e(SettingsManager::class.simpleName, "Error importing db: ", e)
    220                     withContext(Dispatchers.Main) {
    221                         onDbImportError()
    222                     }
    223                     return@launch
    224                 }
    225             }
    226         }
    227     }
    228 
    229     fun clearDb(onSuccess: () -> Unit) {
    230         scope.launch {
    231             when (val response = api.rawRequest("clearDb")) {
    232                 is Success -> {
    233                     onSuccess()
    234                     balanceManager.resetBalances()
    235                 }
    236                 is Error -> {
    237                     Log.e(SettingsManager::class.simpleName, "Error cleaning db: ${response.error}")
    238                     onDbClearError()
    239                 }
    240             }
    241         }
    242     }
    243 
    244     private fun onDbExportError() {
    245         Toast.makeText(context, R.string.settings_db_export_error, LENGTH_LONG).show()
    246     }
    247 
    248     private fun onDbImportError() {
    249         Toast.makeText(context, R.string.settings_db_import_error, LENGTH_LONG).show()
    250     }
    251 
    252     private fun onDbClearError() {
    253         Toast.makeText(context, R.string.settings_db_clear_error, LENGTH_LONG).show()
    254     }
    255 
    256     fun runIntegrationTest(onError: (error: TalerErrorInfo) -> Unit) {
    257         scope.launch {
    258             api.request<Unit>("runIntegrationTestV2") {
    259                 put("amountToWithdraw", "KUDOS:42")
    260                 put("amountToSpend", "KUDOS:23")
    261                 put("corebankApiBaseUrl", "https://bank.demo.taler.net/")
    262                 put("exchangeBaseUrl", "https://exchange.demo.taler.net/")
    263                 put("merchantBaseUrl", "https://backend.demo.taler.net/instances/sandbox/")
    264                 put("merchantAuthToken", "secret-token:sandbox")
    265             }.onError(onError)
    266         }
    267     }
    268 
    269     fun loadPerformanceStats(limit: Int? = 10) {
    270         scope.launch {
    271             api.request(
    272                 "testingGetPerformanceStats",
    273                 TestingGetPerformanceStatsResponse.serializer(),
    274             ) {
    275                 limit?.let { put("limit", limit) }
    276                 this
    277             }.onError { error ->
    278                 Log.e(TAG, "got testingGetPerformanceStats error result $error")
    279             }.onSuccess { res ->
    280                 mPerformanceTable.value = res.stats
    281             }
    282         }
    283     }
    284 }