diff --git a/app/build.gradle b/app/build.gradle
index 1a867113..34e0bc06 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,6 +4,7 @@ apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.gitlab.arturbosch.detekt'
apply plugin: 'kotlin-android'
apply plugin: 'org.jlleitschuh.gradle.ktlint'
+apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
repositories {
google()
@@ -74,6 +75,10 @@ android {
}
}
+ buildFeatures {
+ buildConfig = true
+ }
+
applicationVariants.configureEach { variant ->
variant.outputs.configureEach { output ->
output.outputFileName = "bisq-${variant.name}.apk"
@@ -125,7 +130,7 @@ dependencies {
implementation 'com.google.firebase:firebase-analytics-ktx:22.1.2'
implementation 'com.google.firebase:firebase-messaging:24.1.0'
- implementation 'com.google.code.gson:gson:2.10.1'
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
diff --git a/app/src/androidTest/java/bisq/android/tests/NotificationTest.kt b/app/src/androidTest/java/bisq/android/tests/NotificationTest.kt
index 4dd381ad..c515d100 100644
--- a/app/src/androidTest/java/bisq/android/tests/NotificationTest.kt
+++ b/app/src/androidTest/java/bisq/android/tests/NotificationTest.kt
@@ -40,9 +40,9 @@ import bisq.android.rules.ScreenshotRule
import bisq.android.screens.NotificationTableScreen
import bisq.android.services.BisqFirebaseMessagingService
import bisq.android.util.CryptoUtil
-import bisq.android.util.DateUtil
import com.google.firebase.messaging.RemoteMessage
-import com.google.gson.GsonBuilder
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
import org.assertj.core.api.Assertions.assertThat
import org.junit.After
import org.junit.Before
@@ -168,20 +168,17 @@ class NotificationTest {
private fun buildBisqNotification(): BisqNotification {
val now = Date()
val tradeId = (100000..999999).random()
- val bisqNotification = BisqNotification()
- bisqNotification.type = NotificationType.TRADE.name
- bisqNotification.title = "Trade confirmed"
- bisqNotification.message = "The trade with ID $tradeId is confirmed."
- bisqNotification.sentDate = now.time - 1000 * 60
- bisqNotification.receivedDate = now.time
- return bisqNotification
+ return BisqNotification(
+ type = NotificationType.TRADE.name,
+ title = "Trade confirmed",
+ message = "The trade with ID $tradeId is confirmed.",
+ sentDate = now.time - 1000 * 60,
+ receivedDate = now.time
+ )
}
private fun serializeNotificationPayload(bisqNotification: BisqNotification): String {
- val gsonBuilder = GsonBuilder()
- gsonBuilder.registerTypeAdapter(Date::class.java, DateUtil())
- val gson = gsonBuilder.create()
- return gson.toJson(bisqNotification)
+ return Json.encodeToString(bisqNotification)
}
private fun buildRemoteMessage(bisqNotification: BisqNotification): RemoteMessage {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7b8d526a..37d4f45c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -32,6 +32,7 @@
+
.
+ */
+
+package bisq.android
+
+import android.util.Log
+import bisq.android.database.DebugLog
+import bisq.android.database.DebugLogLevel
+import bisq.android.database.DebugLogRepository
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+class Logging {
+ companion object {
+ private const val TAG = "Logging"
+ }
+
+ // Attempt to initialize debugRepository with the application context.
+ // If the context is unavailable (e.g., in unit tests where Application is not initialized),
+ // catch the exception and log a warning instead of throwing a NullPointerException.
+ // This ensures that tests still work since trying to provide a mocked context is not straight forward.
+ @Suppress("SwallowedException", "TooGenericExceptionCaught")
+ private val debugRepository: DebugLogRepository? = try {
+ val context = Application.applicationContext()
+ DebugLogRepository(context)
+ } catch (e: NullPointerException) {
+ Log.w(TAG, "Skipping debugRepository initialization due to missing context")
+ null
+ }
+
+ fun debug(tag: String, msg: String) {
+ Log.d(tag, msg)
+ insert(DebugLogLevel.DEBUG, msg)
+ }
+
+ fun info(tag: String, msg: String) {
+ Log.i(tag, msg)
+ insert(DebugLogLevel.INFO, msg)
+ }
+
+ fun warn(tag: String, msg: String) {
+ Log.w(tag, msg)
+ insert(DebugLogLevel.WARN, msg)
+ }
+
+ fun error(tag: String, msg: String) {
+ Log.e(tag, msg)
+ insert(DebugLogLevel.ERROR, msg)
+ }
+
+ private fun insert(level: DebugLogLevel, msg: String) {
+ debugRepository?.let {
+ CoroutineScope(Dispatchers.IO).launch {
+ it.insert(
+ DebugLog(
+ timestamp = System.currentTimeMillis(),
+ level = level,
+ text = msg
+ )
+ )
+ }
+ } ?: Log.w(TAG, "Skipping log insert; debugRepository is unavailable")
+ }
+}
diff --git a/app/src/main/java/bisq/android/database/BisqNotification.kt b/app/src/main/java/bisq/android/database/BisqNotification.kt
index 23dd70d0..84c80876 100644
--- a/app/src/main/java/bisq/android/database/BisqNotification.kt
+++ b/app/src/main/java/bisq/android/database/BisqNotification.kt
@@ -20,49 +20,39 @@ package bisq.android.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
-import com.google.gson.Gson
-import com.google.gson.annotations.SerializedName
+import kotlinx.serialization.Serializable
@Entity
+@Serializable
data class BisqNotification(
@PrimaryKey(autoGenerate = true)
- @SerializedName("uid")
var uid: Int = 0,
@ColumnInfo(name = "version")
- @SerializedName("version")
var version: Int = 0,
@ColumnInfo(name = "type")
- @SerializedName("type")
var type: String? = null,
@ColumnInfo(name = "title")
- @SerializedName("title")
var title: String? = null,
@ColumnInfo(name = "message")
- @SerializedName("message")
var message: String? = null,
@ColumnInfo(name = "actionRequired")
- @SerializedName("actionRequired")
var actionRequired: String? = null,
@ColumnInfo(name = "txId")
- @SerializedName("txId")
var txId: String? = null,
@ColumnInfo(name = "receivedDate")
- @SerializedName("receivedDate")
var receivedDate: Long = 0,
@ColumnInfo(name = "sentDate")
- @SerializedName("sentDate")
var sentDate: Long = 0,
@ColumnInfo(name = "read")
- @SerializedName("read")
var read: Boolean = false
) {
override fun equals(other: Any?): Boolean {
@@ -81,8 +71,4 @@ data class BisqNotification(
return listOf(version, type, title, message, actionRequired, txId, sentDate)
.hashCode()
}
-
- override fun toString(): String {
- return "BisqNotification[" + Gson().toJson(this) + "]"
- }
}
diff --git a/app/src/main/java/bisq/android/database/DebugLog.kt b/app/src/main/java/bisq/android/database/DebugLog.kt
new file mode 100644
index 00000000..26b1d2a2
--- /dev/null
+++ b/app/src/main/java/bisq/android/database/DebugLog.kt
@@ -0,0 +1,35 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.database
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import bisq.android.util.DateUtil
+
+@Entity
+data class DebugLog(
+ @PrimaryKey(autoGenerate = true)
+ var id: Int = 0,
+ var timestamp: Long,
+ var level: DebugLogLevel,
+ var text: String
+) {
+ override fun toString(): String {
+ return "${DateUtil.format(timestamp)} [$level] $text"
+ }
+}
diff --git a/app/src/main/java/bisq/android/database/DebugLogDao.kt b/app/src/main/java/bisq/android/database/DebugLogDao.kt
new file mode 100644
index 00000000..303d850b
--- /dev/null
+++ b/app/src/main/java/bisq/android/database/DebugLogDao.kt
@@ -0,0 +1,35 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.database
+
+import androidx.lifecycle.LiveData
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.Query
+
+@Dao
+interface DebugLogDao {
+ @get:Query("SELECT * FROM DebugLog ORDER BY timestamp DESC")
+ val all: LiveData>
+
+ @Insert
+ suspend fun insert(debugLog: DebugLog): Long
+
+ @Query("DELETE FROM DebugLog")
+ suspend fun deleteAll()
+}
diff --git a/app/src/main/java/bisq/android/database/DebugLogDatabase.kt b/app/src/main/java/bisq/android/database/DebugLogDatabase.kt
new file mode 100644
index 00000000..dbcfa9b0
--- /dev/null
+++ b/app/src/main/java/bisq/android/database/DebugLogDatabase.kt
@@ -0,0 +1,48 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.database
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+
+@Database(entities = [DebugLog::class], version = 1, exportSchema = false)
+abstract class DebugLogDatabase : RoomDatabase() {
+
+ abstract fun debugLogDao(): DebugLogDao
+
+ companion object {
+
+ private var instance: DebugLogDatabase? = null
+
+ fun getDatabase(context: Context): DebugLogDatabase {
+ if (instance == null) {
+ synchronized(DebugLogDatabase::class.java) {
+ if (instance == null) {
+ instance = Room.databaseBuilder(
+ context.applicationContext,
+ DebugLogDatabase::class.java, "debug.db"
+ ).build()
+ }
+ }
+ }
+ return instance!!
+ }
+ }
+}
diff --git a/app/src/main/java/bisq/android/database/DebugLogLevel.kt b/app/src/main/java/bisq/android/database/DebugLogLevel.kt
new file mode 100644
index 00000000..2c9c1ea0
--- /dev/null
+++ b/app/src/main/java/bisq/android/database/DebugLogLevel.kt
@@ -0,0 +1,25 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.database
+
+enum class DebugLogLevel {
+ DEBUG,
+ INFO,
+ WARN,
+ ERROR
+}
diff --git a/app/src/main/java/bisq/android/database/DebugLogRepository.kt b/app/src/main/java/bisq/android/database/DebugLogRepository.kt
new file mode 100644
index 00000000..26f6f6c3
--- /dev/null
+++ b/app/src/main/java/bisq/android/database/DebugLogRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.database
+
+import android.content.Context
+import androidx.lifecycle.LiveData
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+class DebugLogRepository(context: Context) {
+
+ private val debugLogDao: DebugLogDao
+
+ val allLogs: LiveData>
+
+ init {
+ val db = DebugLogDatabase.getDatabase(context)
+ debugLogDao = db.debugLogDao()
+ allLogs = debugLogDao.all
+ }
+
+ suspend fun insert(debugLog: DebugLog) = coroutineScope {
+ launch {
+ debugLogDao.insert(debugLog)
+ }
+ }
+
+ suspend fun deleteAll() = coroutineScope {
+ launch { debugLogDao.deleteAll() }
+ }
+}
diff --git a/app/src/main/java/bisq/android/database/NotificationDatabase.kt b/app/src/main/java/bisq/android/database/NotificationDatabase.kt
index 6cbb1de6..2c3b6c78 100644
--- a/app/src/main/java/bisq/android/database/NotificationDatabase.kt
+++ b/app/src/main/java/bisq/android/database/NotificationDatabase.kt
@@ -21,11 +21,8 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
-import androidx.room.TypeConverters
-import bisq.android.util.DateUtil
@Database(entities = [BisqNotification::class], version = 1, exportSchema = false)
-@TypeConverters(DateUtil::class)
abstract class NotificationDatabase : RoomDatabase() {
abstract fun bisqNotificationDao(): BisqNotificationDao
diff --git a/app/src/main/java/bisq/android/services/BisqFirebaseMessagingService.kt b/app/src/main/java/bisq/android/services/BisqFirebaseMessagingService.kt
index b7ff35d3..18074f41 100644
--- a/app/src/main/java/bisq/android/services/BisqFirebaseMessagingService.kt
+++ b/app/src/main/java/bisq/android/services/BisqFirebaseMessagingService.kt
@@ -19,15 +19,16 @@ package bisq.android.services
import android.content.Context
import android.content.Intent
-import android.util.Log
import bisq.android.Application
import bisq.android.Application.Companion.isAppInBackground
+import bisq.android.Logging
import bisq.android.R
import bisq.android.database.BisqNotification
import bisq.android.model.Device
import bisq.android.model.DeviceStatus
import bisq.android.ui.notification.NotificationSender
import bisq.android.ui.welcome.WelcomeActivity
+import bisq.android.util.MaskingUtil.maskSensitive
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailabilityLight
import com.google.android.gms.tasks.OnCompleteListener
@@ -62,7 +63,7 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
fun fetchFcmToken(onComplete: () -> Unit = {}) {
if (!isFirebaseMessagingInitialized()) {
- Log.e(TAG, "FirebaseMessaging is not initialized")
+ Logging().error(TAG, "FirebaseMessaging is not initialized")
onComplete()
return
}
@@ -71,7 +72,7 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
token.addOnCompleteListener(
OnCompleteListener { getTokenTask ->
if (!getTokenTask.isSuccessful) {
- Log.e(TAG, "Fetching FCM token failed: ${getTokenTask.exception}")
+ Logging().error(TAG, "Fetching FCM token failed: ${getTokenTask.exception}")
Device.instance.token = null
onComplete()
tokenBeingFetched = false
@@ -79,21 +80,21 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
}
val token: String? = getTokenTask.result
if (token == null) {
- Log.e(TAG, "FCM token is null")
+ Logging().error(TAG, "FCM token is null")
Device.instance.token = null
onComplete()
tokenBeingFetched = false
return@OnCompleteListener
}
if (Device.instance.token == token) {
- Log.i(TAG, "FCM token has already been fetched")
+ Logging().info(TAG, "FCM token has already been fetched")
onComplete()
tokenBeingFetched = false
return@OnCompleteListener
}
Device.instance.newToken(token)
- Log.i(TAG, "New FCM token: $token")
- Log.i(TAG, "Pairing token: ${Device.instance.pairingToken()}")
+ Logging().info(TAG, "New FCM token: ${maskSensitive(token)}")
+ Logging().info(TAG, "Pairing token: ${maskSensitive(Device.instance.pairingToken())}")
onComplete()
tokenBeingFetched = false
}
@@ -104,7 +105,7 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
@Suppress("ForbiddenComment")
fun refreshFcmToken(onComplete: () -> Unit = {}) {
if (!isFirebaseMessagingInitialized()) {
- Log.e(TAG, "FirebaseMessaging is not initialized")
+ Logging().error(TAG, "FirebaseMessaging is not initialized")
onComplete()
return
}
@@ -121,9 +122,9 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
// FirebaseMessaging.getInstance().apply {
// deleteToken().addOnCompleteListener { deleteTokenTask ->
// if (!deleteTokenTask.isSuccessful) {
-// Log.e(TAG, "Deleting FCM token failed: ${deleteTokenTask.exception}")
+// Logging().error(TAG, "Deleting FCM token failed: ${deleteTokenTask.exception}")
// } else {
-// Log.i(TAG, "FCM token deleted")
+// Logging().debug(TAG, "FCM token deleted")
// }
// fetchFcmToken(onComplete)
// tokenBeingFetched = false
@@ -138,12 +139,12 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
* For more details, see https://firebase.google.com/docs/cloud-messaging/android/receive.
*/
override fun onMessageReceived(remoteMessage: RemoteMessage) {
- Log.i(TAG, "Message received")
+ Logging().debug(TAG, "Message received")
super.onMessageReceived(remoteMessage)
val encryptedData = remoteMessage.data["encrypted"]
if (encryptedData == null) {
- Log.w(TAG, "Message does not contain encrypted data; ${remoteMessage.data}")
+ Logging().warn(TAG, "Received message does not contain encrypted data; ${remoteMessage.data}")
return
}
@@ -151,7 +152,7 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
// If the message contains notification data, then this method is only called while the app is in
// the foreground. Since the app is running and the NotificationReceiver should be registered, only
// need to broadcast the notification so the NotificationReceiver can process it.
- Log.i(
+ Logging().debug(
TAG,
"Notification message received, broadcasting " + getString(R.string.notification_receiver_action)
)
@@ -166,7 +167,7 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
// is in the foreground or background. The NotificationReceiver may not be registered if the app is in the
// background, so cannot simply broadcast the notification. Instead, send it directly to the
// NotificationReceiver.
- Log.i(TAG, "Data message received")
+ Logging().debug(TAG, "Data message received")
Intent().also { notificationIntent ->
notificationIntent.putExtra(
@@ -192,7 +193,7 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
return try {
NotificationProcessor.processNotification(encryptedData)
} catch (e: ProcessingException) {
- e.message?.let { Log.e(TAG, it) }
+ e.message?.let { Logging().error(TAG, it) }
null
}
}
@@ -212,7 +213,10 @@ class BisqFirebaseMessagingService : FirebaseMessagingService() {
override fun onNewToken(newToken: String) {
super.onNewToken(newToken)
if (Device.instance.readFromPreferences(this)) {
- Log.i(TAG, "New FCM token received, app needs to be re-paired: $newToken")
+ Logging().info(
+ TAG,
+ "New FCM token received, app needs to be re-paired: ${maskSensitive(newToken)}"
+ )
Device.instance.reset()
Device.instance.clearPreferences(this)
Device.instance.status = DeviceStatus.NEEDS_REPAIR
diff --git a/app/src/main/java/bisq/android/services/IntentReceiver.kt b/app/src/main/java/bisq/android/services/IntentReceiver.kt
index 566101a0..7a8082d2 100644
--- a/app/src/main/java/bisq/android/services/IntentReceiver.kt
+++ b/app/src/main/java/bisq/android/services/IntentReceiver.kt
@@ -21,8 +21,8 @@ import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.util.Log
import android.widget.Toast
+import bisq.android.Logging
import bisq.android.R
import bisq.android.model.Device
import bisq.android.model.DeviceStatus
@@ -37,12 +37,12 @@ class IntentReceiver(private val activity: Activity? = null) : BroadcastReceiver
@Suppress("ReturnCount")
override fun onReceive(context: Context, intent: Intent) {
- Log.i(TAG, "Intent received")
+ Logging().debug(TAG, "Intent received")
if (intent.action == null ||
!intent.action.equals(context.getString(R.string.intent_receiver_action))
) {
- Log.i(
+ Logging().debug(
TAG,
"Ignoring intent, action is not " + context.getString(R.string.intent_receiver_action)
)
@@ -51,26 +51,26 @@ class IntentReceiver(private val activity: Activity? = null) : BroadcastReceiver
if (intent.hasExtra("error")) {
val errorMessage = intent.getStringExtra("error")
- Log.e(TAG, errorMessage!!)
+ Logging().error(TAG, errorMessage!!)
Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
return
}
if (!intent.hasExtra("type")) {
- Log.i(TAG, "Ignoring intent, missing notification type")
+ Logging().debug(TAG, "Ignoring intent, missing notification type")
return
}
val type = intent.getStringExtra("type")
if (type == NotificationType.SETUP_CONFIRMATION.name && activity is UnpairedBaseActivity) {
- Log.i(TAG, "Pairing confirmed")
+ Logging().debug(TAG, "Pairing confirmed")
activity.pairingConfirmed()
} else if (type == NotificationType.ERASE.name && activity is PairedBaseActivity) {
- Log.i(TAG, "Pairing removed")
+ Logging().debug(TAG, "Pairing removed")
Device.instance.status = DeviceStatus.UNPAIRED
activity.pairingRemoved(context.getString(R.string.pairing_erased))
} else {
- Log.i(TAG, "Ignoring $type notification")
+ Logging().debug(TAG, "Ignoring $type notification")
}
}
}
diff --git a/app/src/main/java/bisq/android/services/NotificationHandler.kt b/app/src/main/java/bisq/android/services/NotificationHandler.kt
index 368cfa7a..0032ec08 100644
--- a/app/src/main/java/bisq/android/services/NotificationHandler.kt
+++ b/app/src/main/java/bisq/android/services/NotificationHandler.kt
@@ -19,9 +19,10 @@ package bisq.android.services
import android.content.Context
import android.content.Intent
-import android.util.Log
+import bisq.android.Logging
import bisq.android.R
import bisq.android.database.BisqNotification
+import bisq.android.database.DebugLogRepository
import bisq.android.database.NotificationRepository
import bisq.android.model.Device
import bisq.android.model.DeviceStatus
@@ -35,40 +36,46 @@ object NotificationHandler {
@Suppress("ReturnCount")
suspend fun handleNotification(bisqNotification: BisqNotification, context: Context) {
val notificationRepository = NotificationRepository(context)
+ val debugRepository = DebugLogRepository(context)
when (bisqNotification.type) {
NotificationType.SETUP_CONFIRMATION.name -> {
- Log.i(TAG, "Setup confirmation")
+ Logging().debug(TAG, "Setup confirmation")
if (Device.instance.token == null) {
- Log.e(TAG, "Device token is null")
+ Logging().error(TAG, "Device token is null")
return
}
if (Device.instance.key == null) {
- Log.e(TAG, "Device key is null")
+ Logging().error(TAG, "Device key is null")
return
}
if (Device.instance.status == DeviceStatus.PAIRED) {
- Log.w(TAG, "Device is already paired")
+ Logging().warn(TAG, "Device is already paired")
return
}
Device.instance.status = DeviceStatus.PAIRED
Device.instance.saveToPreferences(context)
}
NotificationType.ERASE.name -> {
- Log.i(TAG, "Erase pairing")
+ Logging().debug(TAG, "Erase pairing")
Device.instance.reset()
Device.instance.clearPreferences(context)
notificationRepository.deleteAll()
+ debugRepository.deleteAll()
Device.instance.status = DeviceStatus.REMOTE_ERASED
refreshFcmToken()
}
+
+ null -> {
+ Logging().error(TAG, "Notification type is null: $bisqNotification")
+ }
else -> {
- Log.i(TAG, "Inserting ${bisqNotification.type} notification to repository")
+ Logging().debug(TAG, "Inserting ${bisqNotification.type} notification to repository")
notificationRepository.insert(bisqNotification)
}
}
- Log.i(TAG, "Broadcasting " + context.getString(R.string.intent_receiver_action))
+ Logging().debug(TAG, "Broadcasting " + context.getString(R.string.intent_receiver_action))
Intent().also { broadcastIntent ->
broadcastIntent.action = context.getString(R.string.intent_receiver_action)
broadcastIntent.putExtra("type", bisqNotification.type)
diff --git a/app/src/main/java/bisq/android/services/NotificationProcessor.kt b/app/src/main/java/bisq/android/services/NotificationProcessor.kt
index f7869955..fb7eadc4 100644
--- a/app/src/main/java/bisq/android/services/NotificationProcessor.kt
+++ b/app/src/main/java/bisq/android/services/NotificationProcessor.kt
@@ -17,15 +17,14 @@
package bisq.android.services
-import android.util.Log
+import bisq.android.Logging
import bisq.android.database.BisqNotification
import bisq.android.model.Device
import bisq.android.model.NotificationMessage
import bisq.android.model.NotificationMessage.Companion.BISQ_MESSAGE_ANDROID_MAGIC
import bisq.android.util.CryptoUtil
-import bisq.android.util.DateUtil
-import com.google.gson.GsonBuilder
-import com.google.gson.JsonSyntaxException
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.json.Json
import java.text.ParseException
import java.util.Date
@@ -43,14 +42,16 @@ object NotificationProcessor {
notificationMessage.encryptedPayload,
notificationMessage.initializationVector
)
+ Logging().debug(TAG, "Deserializing decrypted notification payload: $decryptedNotificationPayload")
val bisqNotification = deserializeNotificationPayload(decryptedNotificationPayload)
bisqNotification.receivedDate = Date().time
+ Logging().debug(TAG, "Deserialized notification payload: $bisqNotification")
return bisqNotification
} catch (e: Throwable) {
when (e) {
is ParseException, is DecryptingException, is DeserializationException -> {
val message = "Failed to process notification; ${e.message}"
- Log.e(TAG, message)
+ Logging().error(TAG, message)
throw ProcessingException(message)
}
else -> throw e
@@ -97,7 +98,7 @@ object NotificationProcessor {
is IllegalArgumentException,
is CryptoUtil.Companion.CryptoException -> {
val message = "Failed to decrypt notification payload"
- Log.e(TAG, "$message: $encryptedPayload")
+ Logging().error(TAG, "$message: $encryptedPayload")
throw DecryptingException(message)
}
else -> throw e
@@ -107,14 +108,11 @@ object NotificationProcessor {
@Throws(DeserializationException::class)
fun deserializeNotificationPayload(decryptedPayload: String): BisqNotification {
- val gsonBuilder = GsonBuilder()
- gsonBuilder.registerTypeAdapter(Date::class.java, DateUtil())
- val gson = gsonBuilder.create()
try {
- return gson.fromJson(decryptedPayload, BisqNotification::class.java)
- } catch (e: JsonSyntaxException) {
- val message = "Failed to deserialize notification payload"
- Log.e(TAG, "$message: $decryptedPayload")
+ return Json.decodeFromString(decryptedPayload)
+ } catch (e: SerializationException) {
+ val message = "Failed to deserialize notification payload, ${e.message}"
+ Logging().error(TAG, "$message: $decryptedPayload")
throw DeserializationException(message)
}
}
diff --git a/app/src/main/java/bisq/android/services/NotificationReceiver.kt b/app/src/main/java/bisq/android/services/NotificationReceiver.kt
index 6657646e..28c8dfdf 100644
--- a/app/src/main/java/bisq/android/services/NotificationReceiver.kt
+++ b/app/src/main/java/bisq/android/services/NotificationReceiver.kt
@@ -20,7 +20,7 @@ package bisq.android.services
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
-import android.util.Log
+import bisq.android.Logging
import bisq.android.R
import bisq.android.database.BisqNotification
import bisq.android.ext.goAsync
@@ -31,22 +31,23 @@ class NotificationReceiver : BroadcastReceiver() {
private const val TAG = "NotificationReceiver"
}
+ @Suppress("ReturnCount")
override fun onReceive(context: Context, intent: Intent) {
- Log.i(TAG, "Notification received")
+ Logging().debug(TAG, "Notification received")
if (Device.instance.key == null) {
- Log.w(TAG, "Ignoring notification, device does not have a key")
+ Logging().warn(TAG, "Ignoring received notification, device does not have a key")
return
}
- Log.i(TAG, "Processing notification")
+ Logging().debug(TAG, "Processing notification")
val bisqNotification: BisqNotification
try {
bisqNotification = NotificationProcessor.processNotification(
intent.extras?.getString("encrypted").toString()
)
} catch (e: ProcessingException) {
- e.message?.let { Log.e(TAG, it) }
+ e.message?.let { Logging().error(TAG, it) }
Intent().also { broadcastIntent ->
broadcastIntent.action = context.getString(R.string.intent_receiver_action)
broadcastIntent.putExtra(
@@ -58,7 +59,12 @@ class NotificationReceiver : BroadcastReceiver() {
return
}
- Log.i(TAG, "Handling ${bisqNotification.type} notification")
+ if (bisqNotification.type == null) {
+ Logging().error(TAG, "Notification type is null: $bisqNotification")
+ return
+ }
+
+ Logging().debug(TAG, "Handling ${bisqNotification.type} notification")
goAsync {
NotificationHandler.handleNotification(bisqNotification, context)
}
diff --git a/app/src/main/java/bisq/android/ui/debug/DebugActivity.kt b/app/src/main/java/bisq/android/ui/debug/DebugActivity.kt
new file mode 100644
index 00000000..35445ab4
--- /dev/null
+++ b/app/src/main/java/bisq/android/ui/debug/DebugActivity.kt
@@ -0,0 +1,114 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.ui.debug
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Button
+import android.widget.Switch
+import android.widget.TextView
+import androidx.lifecycle.ViewModelProvider
+import bisq.android.R
+import bisq.android.database.DebugLogLevel
+import bisq.android.model.Device
+import bisq.android.ui.BaseActivity
+
+class DebugActivity : BaseActivity() {
+ private lateinit var viewModel: DebugViewModel
+ private lateinit var deviceStatusText: TextView
+ private lateinit var showDebugLogsLabel: TextView
+ private lateinit var showDebugLogsSwitch: Switch
+ private lateinit var logText: TextView
+ private lateinit var clearLogButton: Button
+ private lateinit var sendLogButton: Button
+
+ private var showDebugLogs: Boolean = false
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ viewModel = ViewModelProvider(this)[DebugViewModel::class.java]
+
+ initView()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ viewModel.allLogs.observe(this) { _ ->
+ updateView()
+ }
+ }
+
+ private fun initView() {
+ setContentView(R.layout.activity_debug)
+
+ deviceStatusText = bind(R.id.device_status_value)
+ deviceStatusText.text = Device.instance.status.toString()
+
+ showDebugLogsSwitch = bind(R.id.show_debug_log_switch)
+ showDebugLogsSwitch.setOnCheckedChangeListener { _, isChecked ->
+ showDebugLogs = isChecked
+ updateView()
+ }
+
+ showDebugLogsLabel = bind(R.id.show_debug_log_label)
+ showDebugLogsLabel.setOnClickListener {
+ showDebugLogsSwitch.isChecked = !showDebugLogsSwitch.isChecked
+ }
+
+ logText = bind(R.id.log_text)
+
+ clearLogButton = bind(R.id.clear_log_button)
+ clearLogButton.setOnClickListener {
+ onClearLog()
+ }
+
+ sendLogButton = bind(R.id.send_log_button)
+ sendLogButton.setOnClickListener {
+ onSendLog()
+ }
+ }
+
+ private fun updateView() {
+ val allLogs = viewModel.allLogs.value ?: emptyList()
+
+ val displayedLogs = if (!showDebugLogs) {
+ allLogs.filter { log -> log.level != DebugLogLevel.DEBUG }
+ } else {
+ allLogs
+ }
+
+ logText.text = displayedLogs.joinToString(separator = "\n----------------------------------------------\n") {
+ it.toString()
+ }
+ }
+
+ private fun onClearLog() {
+ viewModel.nukeTable()
+ }
+
+ private fun onSendLog() {
+ val sendIntent: Intent = Intent().apply {
+ action = Intent.ACTION_SEND
+ type = "text/plain"
+ putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_log_subject))
+ putExtra(Intent.EXTRA_TEXT, logText.text)
+ }
+ startActivity(Intent.createChooser(sendIntent, getString(R.string.send_log)))
+ }
+}
diff --git a/app/src/main/java/bisq/android/ui/debug/DebugViewModel.kt b/app/src/main/java/bisq/android/ui/debug/DebugViewModel.kt
new file mode 100644
index 00000000..a8eaed4b
--- /dev/null
+++ b/app/src/main/java/bisq/android/ui/debug/DebugViewModel.kt
@@ -0,0 +1,41 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.ui.debug
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.viewModelScope
+import bisq.android.database.DebugLog
+import bisq.android.database.DebugLogRepository
+import kotlinx.coroutines.launch
+
+class DebugViewModel(application: Application) : AndroidViewModel(application) {
+
+ private val repository: DebugLogRepository = DebugLogRepository(application)
+
+ var allLogs: LiveData> = repository.allLogs
+
+ fun insert(debugLog: DebugLog) = viewModelScope.launch {
+ repository.insert(debugLog)
+ }
+
+ fun nukeTable() = viewModelScope.launch {
+ repository.deleteAll()
+ }
+}
diff --git a/app/src/main/java/bisq/android/ui/notification/NotificationSender.kt b/app/src/main/java/bisq/android/ui/notification/NotificationSender.kt
index d7d7ed9e..1aea27c7 100644
--- a/app/src/main/java/bisq/android/ui/notification/NotificationSender.kt
+++ b/app/src/main/java/bisq/android/ui/notification/NotificationSender.kt
@@ -22,11 +22,11 @@ import android.app.PendingIntent
import android.content.Intent
import android.content.pm.PackageManager
import android.media.RingtoneManager
-import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import bisq.android.Application
+import bisq.android.Logging
import bisq.android.R
import java.util.Date
@@ -42,7 +42,7 @@ object NotificationSender {
Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
- Log.d(TAG, "*** Unable to send notification; POST_NOTIFICATIONS permission not granted")
+ Logging().warn(TAG, "*** Unable to send notification; POST_NOTIFICATIONS permission not granted")
return
}
diff --git a/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt b/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt
index 7626e31f..01a336e9 100644
--- a/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt
+++ b/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt
@@ -193,10 +193,12 @@ class NotificationTableActivity : PairedBaseActivity() {
@Suppress("MagicNumber")
private fun addExampleNotifications() {
for (counter in 1..5) {
- val now = Date()
- val bisqNotification = BisqNotification()
- bisqNotification.receivedDate = now.time + counter * 1000
- bisqNotification.sentDate = bisqNotification.receivedDate - 1000 * 30
+ val receivedDate = Date().time + counter * 1000
+ val bisqNotification = BisqNotification(
+ type = "",
+ receivedDate = receivedDate,
+ sentDate = receivedDate - 1000 * 30
+ )
when (counter) {
1 -> {
bisqNotification.type = NotificationType.TRADE.name
diff --git a/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt b/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt
index 70d2130d..c11a6db8 100644
--- a/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt
@@ -28,6 +28,7 @@ import androidx.preference.PreferenceFragmentCompat
import bisq.android.Application
import bisq.android.BISQ_MOBILE_URL
import bisq.android.BISQ_NETWORK_URL
+import bisq.android.BuildConfig
import bisq.android.R
import bisq.android.model.Device
import bisq.android.model.DeviceStatus
@@ -35,6 +36,8 @@ import bisq.android.services.BisqFirebaseMessagingService
import bisq.android.ui.DialogBuilder
import bisq.android.ui.ThemeProvider
import bisq.android.ui.UiUtil.loadWebPage
+import bisq.android.ui.debug.DebugActivity
+import bisq.android.ui.debug.DebugViewModel
import bisq.android.ui.notification.NotificationViewModel
import bisq.android.ui.pairing.PairingScanActivity
import bisq.android.ui.welcome.WelcomeActivity
@@ -57,15 +60,20 @@ class SettingsFragment : PreferenceFragmentCompat() {
private val aboutAppPreference by lazy {
findPreference(getString(R.string.about_app_preferences_key))
}
+ private val debugPreference by lazy {
+ findPreference(getString(R.string.debug_preferences_key))
+ }
private val versionPreference by lazy {
findPreference(getString(R.string.version_preferences_key))
}
- private lateinit var viewModel: NotificationViewModel
+ private lateinit var notificationViewModel: NotificationViewModel
+ private lateinit var debugViewModel: DebugViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- viewModel = ViewModelProvider(this)[NotificationViewModel::class.java]
+ notificationViewModel = ViewModelProvider(this)[NotificationViewModel::class.java]
+ debugViewModel = ViewModelProvider(this)[DebugViewModel::class.java]
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@@ -75,6 +83,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
setScanPairingTokenPreference()
setAboutBisqPreference()
setAboutAppPreference()
+ setDebugPreference()
setVersionPreference()
}
@@ -114,7 +123,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
{ _, _ ->
Device.instance.reset()
Device.instance.clearPreferences(context)
- viewModel.nukeTable()
+ notificationViewModel.nukeTable()
+ debugViewModel.nukeTable()
Device.instance.status = DeviceStatus.ERASED
BisqFirebaseMessagingService.refreshFcmToken()
val intent = Intent(context, WelcomeActivity::class.java)
@@ -145,6 +155,19 @@ class SettingsFragment : PreferenceFragmentCompat() {
}
}
+ private fun setDebugPreference() {
+ if (!BuildConfig.DEBUG) {
+ debugPreference?.let { preferenceScreen.removePreference(it) }
+ } else {
+ debugPreference?.setOnPreferenceClickListener {
+ this.context?.let { context ->
+ startActivity(Intent(context, DebugActivity::class.java))
+ }
+ true
+ }
+ }
+ }
+
private fun setVersionPreference() {
versionPreference?.title = getString(R.string.version, Application.getAppVersion())
}
diff --git a/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt b/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt
index 00076b1b..4102aae7 100644
--- a/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt
+++ b/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt
@@ -22,13 +22,14 @@ import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
-import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.Toast
import androidx.core.content.ContextCompat
import bisq.android.BISQ_MOBILE_URL
+import bisq.android.BuildConfig
+import bisq.android.Logging
import bisq.android.R
import bisq.android.model.Device
import bisq.android.model.DeviceStatus
@@ -38,11 +39,13 @@ import bisq.android.services.BisqFirebaseMessagingService.Companion.isTokenBeing
import bisq.android.ui.DialogBuilder
import bisq.android.ui.UiUtil.loadWebPage
import bisq.android.ui.UnpairedBaseActivity
+import bisq.android.ui.debug.DebugActivity
import bisq.android.ui.notification.NotificationTableActivity
import bisq.android.ui.pairing.PairingScanActivity
@Suppress("TooManyFunctions")
class WelcomeActivity : UnpairedBaseActivity() {
+ private lateinit var debugButton: Button
private lateinit var learnMoreButton: Button
private lateinit var pairButton: Button
private lateinit var progressBar: ProgressBar
@@ -104,6 +107,23 @@ class WelcomeActivity : UnpairedBaseActivity() {
private fun initView() {
setContentView(R.layout.activity_welcome)
+ debugButton = bind(R.id.welcome_debug_button)
+ debugButton.setOnClickListener {
+ startActivity(
+ Intent(
+ this,
+ DebugActivity::class.java
+ )
+ )
+ }
+ if (BuildConfig.DEBUG) {
+ debugButton.visibility = View.VISIBLE
+ debugButton.isEnabled = true
+ } else {
+ debugButton.visibility = View.GONE
+ debugButton.isEnabled = false
+ }
+
pairButton = bind(R.id.welcome_pair_button)
if (isGooglePlayServicesAvailable(this)) {
pairButton.setOnClickListener {
@@ -144,7 +164,7 @@ class WelcomeActivity : UnpairedBaseActivity() {
}
private fun fetchFcmToken(onFetchFcmTokenComplete: () -> Unit = {}) {
- Log.i(TAG, "Fetching FCM token")
+ Logging().info(TAG, "Fetching FCM token")
disablePairButton()
BisqFirebaseMessagingService.fetchFcmToken {
enablePairButton()
@@ -194,10 +214,10 @@ class WelcomeActivity : UnpairedBaseActivity() {
private fun maybeProcessOpenedNotification() {
val extras = intent.extras
if (extras != null) {
- Log.i(TAG, "Processing opened notification")
+ Logging().debug(TAG, "Processing opened notification")
val notificationMessage = extras.getString("encrypted")
if (notificationMessage != null) {
- Log.i(TAG, "Broadcasting " + getString(R.string.notification_receiver_action))
+ Logging().debug(TAG, "Broadcasting " + getString(R.string.notification_receiver_action))
Intent().also { broadcastIntent ->
broadcastIntent.action = getString(R.string.notification_receiver_action)
broadcastIntent.flags = Intent.FLAG_INCLUDE_STOPPED_PACKAGES
diff --git a/app/src/main/java/bisq/android/util/DateUtil.kt b/app/src/main/java/bisq/android/util/DateUtil.kt
index 75f10957..8f6c5cb5 100644
--- a/app/src/main/java/bisq/android/util/DateUtil.kt
+++ b/app/src/main/java/bisq/android/util/DateUtil.kt
@@ -17,63 +17,22 @@
package bisq.android.util
-import android.util.Log
-import androidx.room.TypeConverter
-import com.google.gson.JsonDeserializationContext
-import com.google.gson.JsonDeserializer
-import com.google.gson.JsonElement
-import com.google.gson.JsonParseException
-import java.lang.reflect.Type
-import java.text.ParseException
import java.text.SimpleDateFormat
-import java.util.Date
import java.util.Locale
import java.util.TimeZone
-class DateUtil : JsonDeserializer {
-
- companion object {
- private const val TAG = "DateDeserializer"
- private val LOCALE = Locale.US
- private const val PATTERN = "yyyy-MM-dd HH:mm:ss"
-
- fun format(
- date: Long,
- locale: Locale = LOCALE,
- pattern: String = PATTERN,
- timezone: TimeZone = TimeZone.getDefault()
- ): String? {
- val formatter = SimpleDateFormat(pattern, locale)
- formatter.timeZone = timezone
- return formatter.format(date)
- }
- }
-
- @TypeConverter
- fun toDate(value: Long?): Date? {
- return if (value == null) null else Date(value)
- }
-
- @TypeConverter
- fun toLong(value: Date?): Long? {
- return value?.time
- }
-
- @Throws(JsonParseException::class)
- override fun deserialize(
- element: JsonElement,
- arg1: Type?,
- arg2: JsonDeserializationContext?
- ): Date? {
- val date = element.asString
- val formatter = SimpleDateFormat(PATTERN, LOCALE)
- formatter.timeZone = TimeZone.getDefault()
-
- return try {
- formatter.parse(date)
- } catch (e: ParseException) {
- Log.e(TAG, "Failed to parse date: $e")
- null
- }
+object DateUtil {
+ private val LOCALE = Locale.US
+ private const val PATTERN = "yyyy-MM-dd HH:mm:ss"
+
+ fun format(
+ date: Long,
+ locale: Locale = LOCALE,
+ pattern: String = PATTERN,
+ timezone: TimeZone = TimeZone.getDefault()
+ ): String? {
+ val formatter = SimpleDateFormat(pattern, locale)
+ formatter.timeZone = timezone
+ return formatter.format(date)
}
}
diff --git a/app/src/main/java/bisq/android/util/MaskingUtil.kt b/app/src/main/java/bisq/android/util/MaskingUtil.kt
new file mode 100644
index 00000000..f64011bf
--- /dev/null
+++ b/app/src/main/java/bisq/android/util/MaskingUtil.kt
@@ -0,0 +1,37 @@
+/*
+ * This file is part of Bisq.
+ *
+ * Bisq is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or (at
+ * your option) any later version.
+ *
+ * Bisq 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 Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Bisq. If not, see .
+ */
+
+package bisq.android.util
+
+object MaskingUtil {
+ @Suppress("ReturnCount")
+ fun maskSensitive(value: String?, visibleChars: Int = 5, maskChar: Char = '*'): String {
+ if (value.isNullOrEmpty()) return value ?: ""
+
+ // If the value is too short to be masked, mask the whole thing
+ val minVisible = visibleChars * 2
+ if (value.length <= minVisible) {
+ return maskChar.toString().repeat(value.length)
+ }
+
+ val firstPart = value.take(visibleChars)
+ val lastPart = value.takeLast(visibleChars)
+ val maskedMiddle = maskChar.toString().repeat(value.length - (visibleChars * 2))
+
+ return "$firstPart$maskedMiddle$lastPart"
+ }
+}
diff --git a/app/src/main/res/drawable/ic_debug_24.xml b/app/src/main/res/drawable/ic_debug_24.xml
new file mode 100644
index 00000000..be93fc8d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_debug_24.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml
new file mode 100644
index 00000000..08ee8e19
--- /dev/null
+++ b/app/src/main/res/layout/activity_debug.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml
index c7a0c3f7..60d4422e 100644
--- a/app/src/main/res/layout/activity_welcome.xml
+++ b/app/src/main/res/layout/activity_welcome.xml
@@ -24,7 +24,6 @@
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/bisq_logo_green" />
-
+