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 }