From a803f534cf4d75ab05eb18df9e82b922b9f20a40 Mon Sep 17 00:00:00 2001 From: acrespo Date: Mon, 2 Dec 2024 18:12:29 -0300 Subject: [PATCH] Apollo: Release source code for 52.5 --- android/CHANGELOG.md | 23 +++++++++- .../BackgroundExecutionMetricsInterceptor.kt | 42 +++++++++++++++-- .../os/BackgroundExecutionMetricsProvider.kt | 40 +++++++++++++++- .../muun/apollo/data/os/BuildInfoProvider.kt | 2 +- .../io/muun/apollo/data/os/NfcProvider.kt | 46 +++++++++++++++++++ .../main/java/io/muun/apollo/data/os/OS.kt | 11 +++++ .../apollo/data/os/TelephonyInfoProvider.kt | 14 +++++- .../muun/apollo/domain/EmailReportManager.kt | 12 ++++- .../apollo/domain/analytics/AnalyticsEvent.kt | 5 +- .../errors/data/MuunSerializationError.kt | 17 +++++++ .../domain/libwallet/LibwalletBridge.java | 1 + .../apollo/domain/model/report/CrashReport.kt | 32 +++++++++++-- .../domain/model/report/CrashReportBuilder.kt | 4 -- .../apollo/domain/model/report/EmailReport.kt | 4 +- android/apolloui/build.gradle | 4 +- .../presentation/ui/base/BaseFragment.java | 3 +- .../presentation/ui/base/BasePresenter.java | 30 +++++++++--- libwallet/encrypt.go | 3 ++ libwallet/init.go | 2 +- 19 files changed, 258 insertions(+), 37 deletions(-) create mode 100644 android/apollo/src/main/java/io/muun/apollo/data/os/NfcProvider.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/errors/data/MuunSerializationError.kt diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 63a3b43c..39748186 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -6,14 +6,33 @@ follow [https://changelog.md/](https://changelog.md/) guidelines. ## [Unreleased] -## [52.4] - 2024-10-18 +## [52.5] - 2024-12-02 + +### ADDED + +- Reintroduced background notification processing reliability improvements + +### FIXED + +- Errors due to non ascii chars in http headers. +- Error regarding radioVersion in some devices. +- Error regarding getDataState in some devices. +- Error regarding TransactionTooLargeException in email report intent. + +### CHANGED + +- Made json serialization of background execution metrics more reliable. +- Added troubleshooting logs for decryption of operation metadata ciphertext. +- Trimmed stacktraces in error reports breadcrumbs and metadata + +## [52.4] - 2024-11-18 ### FIXED - Removed some background notification processing reliability improvements that were causing errors and crashes -## [52.3] - 2024-10-08 +## [52.3] - 2024-11-08 ### ADDED diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/BackgroundExecutionMetricsInterceptor.kt b/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/BackgroundExecutionMetricsInterceptor.kt index 62284820..ed8eca1a 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/BackgroundExecutionMetricsInterceptor.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/BackgroundExecutionMetricsInterceptor.kt @@ -2,21 +2,55 @@ package io.muun.apollo.data.net.base.interceptor import io.muun.apollo.data.net.base.BaseInterceptor import io.muun.apollo.data.os.BackgroundExecutionMetricsProvider +import io.muun.apollo.domain.errors.data.MuunSerializationError +import io.muun.apollo.domain.model.user.User +import io.muun.apollo.domain.selector.UserSelector import io.muun.common.net.HeaderUtils import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okhttp3.Request +import timber.log.Timber import javax.inject.Inject class BackgroundExecutionMetricsInterceptor @Inject constructor( private val bemProvider: BackgroundExecutionMetricsProvider, + private val userSel: UserSelector, ) : BaseInterceptor() { override fun processRequest(originalRequest: Request): Request { return originalRequest.newBuilder() - .addHeader( - HeaderUtils.BACKGROUND_EXECUTION_METRICS, - Json.encodeToString(bemProvider.run()) - ).build() + .addBem(originalRequest) + .build() + } + + private fun Request.Builder.addBem(originalRequest: Request): Request.Builder { + val encodeToJson = safelyEncodeJson(originalRequest) + + return if (encodeToJson != null) { + try { + addHeader(HeaderUtils.BACKGROUND_EXECUTION_METRICS, encodeToJson) + } catch (e: Throwable) { + logError(originalRequest, e) + this + } + } else { + this + } + } + + private fun safelyEncodeJson(originalRequest: Request): String? { + return try { + Json.encodeToString(bemProvider.run()) + } catch (e: Throwable) { + logError(originalRequest, e) + null + } + } + + private fun logError(originalRequest: Request, e: Throwable) { + val supportId = userSel.getOptional() + .flatMap { obj: User -> obj.supportId } + .orElse("Not logged in") + Timber.e(MuunSerializationError(supportId, originalRequest, e)) } } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt index 4b9e7ff0..4631afa6 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/BackgroundExecutionMetricsProvider.kt @@ -28,6 +28,8 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val systemCapabilitiesProvider: SystemCapabilitiesProvider, private val dateTimeZoneProvider: DateTimeZoneProvider, private val localeInfoProvider: LocaleInfoProvider, + private val trafficStatsInfoProvider: TrafficStatsInfoProvider, + private val nfcProvider: NfcProvider, ) { private val powerManager: PowerManager by lazy { @@ -72,7 +74,24 @@ class BackgroundExecutionMetricsProvider @Inject constructor( connectivityInfoProvider.proxySocks, dateTimeZoneProvider.autoDateTime, dateTimeZoneProvider.autoTimeZone, - dateTimeZoneProvider.timeZoneId + dateTimeZoneProvider.timeZoneId, + localeInfoProvider.dateFormat, + localeInfoProvider.regionCode, + dateTimeZoneProvider.calendarIdentifier, + trafficStatsInfoProvider.androidMobileRxTraffic, + telephonyInfoProvider.simOperatorId, + telephonyInfoProvider.simOperatorName, + telephonyInfoProvider.mobileNetworkId, + telephonyInfoProvider.mobileNetworkName, + telephonyInfoProvider.mobileRoaming, + telephonyInfoProvider.mobileDataStatus, + telephonyInfoProvider.mobileRadioType, + telephonyInfoProvider.mobileDataActivity, + connectivityInfoProvider.networkLink, + nfcProvider.hasNfcFeature(), + nfcProvider.hasNfcAdapter, + nfcProvider.isNfcEnabled, + nfcProvider.getNfcAntennaPosition().map { "${it.first};${it.second}" }.toTypedArray() ) @Suppress("ArrayInDataClass") @@ -114,7 +133,24 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val proxySocks: String, private val autoDateTime: Int, private val autoTimeZone: Int, - private val timeZoneId: String + private val timeZoneId: String, + private val androidDateFormat: String, + private val regionCode: String, + private val androidCalendarIdentifier: String, + private val androidMobileRxTraffic: Long, + private val androidSimOperatorId: String, + private val androidSimOperatorName: String, + private val androidMobileOperatorId: String, + private val mobileOperatorName: String, + private val androidMobileRoaming: Boolean, + private val androidMobileDataStatus: Int, + private val androidMobileRadioType: Int, + private val androidMobileDataActivity: Int, + private val androidNetworkLink: ConnectivityInfoProvider.NetworkLink?, + private val androidHasNfcFeature: Boolean, + private val androidHasNfcAdapter: Boolean, + private val androidNfcEnabled: Boolean, + private val androidNfcAntennaPositions: Array ) /** diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/BuildInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/BuildInfoProvider.kt index b8a76f7d..899f154c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/BuildInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/BuildInfoProvider.kt @@ -18,7 +18,7 @@ class BuildInfoProvider @Inject constructor() { val time: Long, val host: String, val type: String, - val radioVersion: String, + val radioVersion: String?, val securityPatch: String, val baseOs: String, ) diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/NfcProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/NfcProvider.kt new file mode 100644 index 00000000..66e36a47 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/NfcProvider.kt @@ -0,0 +1,46 @@ +package io.muun.apollo.data.os + +import android.content.Context +import android.content.pm.PackageManager +import android.nfc.NfcAdapter +import timber.log.Timber +import javax.inject.Inject + +class NfcProvider @Inject constructor(private val context: Context) { + + // We could use getSystemService(Context.NFC_SERVICE) as? NfcManager but it's just a wrapper + // of this. + private val nfcAdapter: NfcAdapter? = try { + NfcAdapter.getDefaultAdapter(context) + } catch (e: Throwable) { + null + } + + fun hasNfcFeature(): Boolean { + val packageManager = context.packageManager + return packageManager.hasSystemFeature(PackageManager.FEATURE_NFC) + } + + val hasNfcAdapter: Boolean + get() = nfcAdapter != null + + val isNfcEnabled: Boolean + get() = nfcAdapter != null && nfcAdapter.isEnabled + + fun getNfcAntennaPosition(): List> { + val result = mutableListOf>() + + try { + if (OS.supportsAvailableNfcAntennas()) { + val antennas = nfcAdapter?.nfcAntennaInfo?.availableNfcAntennas.orEmpty() + for (antenna in antennas) { + result.add(Pair(antenna.locationX.toFloat(), antenna.locationY.toFloat())) + } + } + } catch (e: Exception) { + Timber.i("Error while reading NFC data from NFC compat device: ${e.message}") + } + + return result + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt index f126cefe..c2da5b35 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt @@ -229,6 +229,10 @@ object OS { fun supportsKeystoreExceptionPublicMethods(): Boolean = isAndroidTiramisuOrNewer() + fun supportsAvailableNfcAntennas(): Boolean = + isAndroidUpsideDownCakeOrNewer() + + // PRIVATE STUFF: /** @@ -313,4 +317,11 @@ object OS { @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) private fun isAndroidTiramisuOrNewer(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + + /** + * Whether this OS version is U-14-34 or newer. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + private fun isAndroidUpsideDownCakeOrNewer(): Boolean = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt index 8a465543..5ae006ab 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/TelephonyInfoProvider.kt @@ -6,6 +6,7 @@ import io.muun.common.Optional import javax.inject.Inject private const val UNKNOWN = "UNKNOWN" +private const val DATA_UNKNOWN = -1 // TODO open to make tests work with mockito. We should probably move to mockK open class TelephonyInfoProvider @Inject constructor(context: Context) { @@ -31,8 +32,17 @@ open class TelephonyInfoProvider @Inject constructor(context: Context) { } val dataState: String - get() { - return mapDataState(telephonyManager.dataState) + get() = try { + mapDataState(telephonyManager.dataState) + } catch (e: Exception) { + // 1. Docs mention UnsupportedOperationException If the device does not have + // PackageManager#FEATURE_TELEPHONY_DATA. + // 2. Undocumented, but we've observed SecurityException: Requires READ_PHONE_STATE in + // the wild, in some Samsung Android 5 devices. So, catching that as well. + + // Using our own custom DATA_UNKNOWN instead of TelephonyManager.DATA_UNKNOWN as it was + // only added in API 29. + mapDataState(DATA_UNKNOWN) } val simRegion: String diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/EmailReportManager.kt b/android/apollo/src/main/java/io/muun/apollo/domain/EmailReportManager.kt index 228d0ea2..fe82d79e 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/EmailReportManager.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/EmailReportManager.kt @@ -26,7 +26,15 @@ class EmailReportManager @Inject constructor( private val context: Context, ) { - fun buildEmailReport(report: CrashReport, presenterName: String): EmailReport { + fun buildAbridgedEmailReport(report: CrashReport, presenterName: String): EmailReport { + return buildEmailReport(report, presenterName, abridged = true) + } + + fun buildEmailReport( + report: CrashReport, + presenterName: String, + abridged: Boolean = false, + ): EmailReport { val supportId = userSel.getOptional() .flatMap { obj: User -> obj.supportId } @@ -47,7 +55,7 @@ class EmailReportManager @Inject constructor( .defaultRegion(telephonyInfoProvider.region.orElse("null")) .rootHint(isRootedDeviceAction.actionNow()) .locale(context.locale()) - .build() + .build(abridged) } private fun getFcmTokenHash() = try { diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/analytics/AnalyticsEvent.kt b/android/apollo/src/main/java/io/muun/apollo/domain/analytics/AnalyticsEvent.kt index e369eb10..df6c1c4b 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/analytics/AnalyticsEvent.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/analytics/AnalyticsEvent.kt @@ -6,7 +6,7 @@ import io.muun.apollo.domain.model.NightMode import io.muun.apollo.domain.model.PaymentRequest import io.muun.apollo.domain.model.report.CrashReport import io.muun.common.model.OperationDirection -import java.util.* +import java.util.Locale /** * AnalyticsEvent list, shared with Falcon. Names are lower-cased into FBA event IDs, do not rename. @@ -516,7 +516,8 @@ sealed class AnalyticsEvent(metadataKeyValues: List> = listOf( "title" to report.getTrackingTitle(), "tag" to report.tag, "message" to report.message, - "error" to report.printError().replace("\n", " "), + // Trim error stacktraces to avoid problems (not ideal but should be more than enough) + "error" to report.printErrorForAnalytics(), "metadata" to report.printMetadata() ) ) diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/errors/data/MuunSerializationError.kt b/android/apollo/src/main/java/io/muun/apollo/domain/errors/data/MuunSerializationError.kt new file mode 100644 index 00000000..3dfade6d --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/errors/data/MuunSerializationError.kt @@ -0,0 +1,17 @@ +package io.muun.apollo.domain.errors.data + +import io.muun.apollo.domain.errors.MuunError +import io.muun.common.exception.PotentialBug +import okhttp3.Request + +class MuunSerializationError( + supportId: String, + originalRequest: Request, + cause: Throwable, +) : MuunError(cause), PotentialBug { + + init { + metadata["supportId"] = supportId + metadata["request"] = originalRequest.url().uri().toString() + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletBridge.java b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletBridge.java index 22d89f7c..f40027e7 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletBridge.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletBridge.java @@ -330,6 +330,7 @@ public static byte[] decryptPayloadFromPeer(final PrivateKey userKey, final NetworkParameters network) { try { + Timber.i("Decrypting payload from peer: %s", payload); // TODO: We should actually pass a peer key here return toLibwalletModel(userKey, network) .decrypterFrom(null) diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt index 21c30aba..b2314f82 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReport.kt @@ -2,6 +2,15 @@ package io.muun.apollo.domain.model.report import android.util.Log import java.io.Serializable +import kotlin.math.min + +/** + * This acts as a safe-guard when sending errors in our own email reports. + * Too many data can cause crashes (e.g TransactionTooLargeException in when trying use emailIntent + * for email reports). + */ +private const val STACK_TRACE_LIMIT_FOR_EMAIL_REPORTS = 10_000 +private const val STACK_TRACE_LIMIT_FOR_ANALYTICS = 500 data class CrashReport( val tag: String, @@ -11,8 +20,8 @@ data class CrashReport( val metadata: MutableMap, ) { - fun print() = - "Tag:$tag\nMessage:$message\nError:${printError()}\nMetadata:{\n\n${printMetadata()}}" + fun print(abridged: Boolean) = + "Message:$message\nError:${printError(abridged)}\nMetadata:{\n\n${printMetadata()}}" fun printMetadata(): String { val builder = StringBuilder() @@ -22,8 +31,23 @@ data class CrashReport( return builder.toString() } - fun printError(): String = - Log.getStackTraceString(error) + private fun printError(abridged: Boolean = false): String { + val stackTraceString = Log.getStackTraceString(error) + return if (abridged) { + val stackTraceCapSizeInChars = + min(STACK_TRACE_LIMIT_FOR_EMAIL_REPORTS, stackTraceString.length) + return "${stackTraceString.substring(0, stackTraceCapSizeInChars)}\nEXCEEDED MAX LENGTH" + } else { + stackTraceString + } + } + + fun printErrorForAnalytics(): String { + val stackTraceString = Log.getStackTraceString(error) + val stackTraceCapSizeInChars = min(STACK_TRACE_LIMIT_FOR_ANALYTICS, stackTraceString.length) + return stackTraceString.substring(0, stackTraceCapSizeInChars) + .replace("\n", " ") + } fun getTrackingTitle(): String = error.javaClass.simpleName + ":" + error.localizedMessage?.replace("\n", " ") diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt index 4c9676ac..11ef5b4a 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/CrashReportBuilder.kt @@ -58,10 +58,6 @@ object CrashReportBuilder { // Prepare the error: var error = origError ?: WrappedErrorMessage(message) - if (error.stackTrace == null) { - error.fillInStackTrace() - } - error = when (error) { is HttpException -> ApiError(error) is UnknownCurrencyException -> MissingCurrencyError(error) diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/EmailReport.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/EmailReport.kt index ae20b8ac..b1df67f2 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/report/EmailReport.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/report/EmailReport.kt @@ -88,7 +88,7 @@ class EmailReport private constructor(val body: String) { fun locale(locale: Locale) = apply { this.locale = locale } @CheckReturnValue - fun build(): EmailReport { + fun build(abridged: Boolean): EmailReport { checkNotNull(report) checkNotNull(fcmTokenHash) @@ -127,7 +127,7 @@ class EmailReport private constructor(val body: String) { |Rooted (just a hint, no guarantees): $rootHint |Unsupported Currencies: ${getUnsupportedCurrencies(report!!).contentToString()} |Default Region: $defaultRegion - |${report!!.print()}""".trimMargin() + |${report!!.print(abridged)}""".trimMargin() Timber.d("EmailReport: \n$body") return EmailReport(body) diff --git a/android/apolloui/build.gradle b/android/apolloui/build.gradle index 78489578..1bd312f5 100644 --- a/android/apolloui/build.gradle +++ b/android/apolloui/build.gradle @@ -94,8 +94,8 @@ android { applicationId "io.muun.apollo" minSdk 19 targetSdk 34 - versionCode 1204 - versionName "52.4" + versionCode 1205 + versionName "52.5" // Needed to make sure these classes are available in the main DEX file for API 19 // See: https://spin.atomicobject.com/2018/07/16/support-kitkat-multidex/ diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java index 121320c5..734715d7 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseFragment.java @@ -40,7 +40,6 @@ import rx.functions.Action1; import timber.log.Timber; -import java.util.Objects; import javax.inject.Inject; import javax.validation.constraints.NotNull; @@ -410,7 +409,7 @@ protected FragmentComponent getComponent() { @NotNull private ApplicationComponent getApplicationComponent() { - return ((BaseActivity) Objects.requireNonNull(getActivity())).getApplicationComponent(); + return ((BaseActivity) requireActivity()).getApplicationComponent(); } /** diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java index 6611218a..a1fb1f48 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java @@ -38,7 +38,6 @@ import android.content.pm.PackageManager; import android.net.NetworkInfo; import android.os.Bundle; -import android.util.Log; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -267,11 +266,13 @@ public void handleError(Throwable error) { Timber.e(error); } + final CrashReport errorReport = CrashReportBuilder.INSTANCE.build(error); + analytics.report(new AnalyticsEvent.E_ERROR( AnalyticsEvent.ERROR_TYPE.GENERIC, error.getClass().getSimpleName(), error.getLocalizedMessage(), - Log.getStackTraceString(error).replace("\n", " ") + errorReport.printErrorForAnalytics() )); // Our current error handling logic is this: @@ -508,10 +509,12 @@ protected Observable.Transformer, T> handleStates( */ private void showErrorReportDialog(Throwable error, boolean standalone) { + final CrashReport errorReport = CrashReportBuilder.INSTANCE.build(error); + analytics.report(new AnalyticsEvent.E_ERROR_REPORT_DIALOG( error.getClass().getSimpleName(), error.getLocalizedMessage(), - Log.getStackTraceString(error).replace("\n", " ") + errorReport.printErrorForAnalytics() )); final MuunDialog.Builder builder = new MuunDialog.Builder() @@ -542,10 +545,23 @@ public void sendErrorReport(Throwable error) { analytics.attachAnalyticsMetadata(report); final EmailReport emailReport = emailReportManager - .buildEmailReport(report, this.getClass().getSimpleName()); - - final Intent emailIntent = Email.INSTANCE.buildEmailReportIntent(getContext(), emailReport); - logcat.addLogsAsAttachment(emailIntent); + .buildEmailReport(report, this.getClass().getSimpleName(), false); + + Intent emailIntent = Email.INSTANCE.buildEmailReportIntent(getContext(), emailReport); + + try { + logcat.addLogsAsAttachment(emailIntent); + } catch (Throwable t) { + // Avoid crashing if ANYTHING goes wrong while trying to attach logs to email + Timber.i("Error while attaching logs: %s", t.getMessage()); + + // Build EmailReport with stacktrace "trimmed" to avoid crashing at startActivity + // Note: We may have to cut down on breadcrumbs or other metadata if this is still + // a problem. + final EmailReport abridgedEmailReport = emailReportManager + .buildAbridgedEmailReport(report, this.getClass().getSimpleName()); + emailIntent = Email.INSTANCE.buildEmailReportIntent(getContext(), abridgedEmailReport); + } if (Email.INSTANCE.hasEmailAppInstalled(getContext())) { getContext().startActivity(emailIntent); diff --git a/libwallet/encrypt.go b/libwallet/encrypt.go index e466acd2..2a4ccbc4 100644 --- a/libwallet/encrypt.go +++ b/libwallet/encrypt.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "log/slog" "math" "math/big" @@ -208,6 +209,8 @@ func (d *hdPrivKeyDecrypter) Decrypt(payload string) ([]byte, error) { // Uses AES128-GCM with associated data. ECDHE is used for key exchange and ECDSA for authentication. // See Encrypt further up for an in depth dive into the scheme used + slog.Info("Libwallet: Decrypting payload " + payload) + decoded := base58.Decode(payload) reader := bytes.NewReader(decoded) version, err := reader.ReadByte() diff --git a/libwallet/init.go b/libwallet/init.go index 68379c4c..c5b9cb71 100644 --- a/libwallet/init.go +++ b/libwallet/init.go @@ -27,7 +27,7 @@ func Init(c *Config) { Cfg = c if Cfg.AppLogSink != nil { - logger := slog.New(NewBridgeLogHandler(Cfg.AppLogSink, slog.LevelWarn)) + logger := slog.New(NewBridgeLogHandler(Cfg.AppLogSink, slog.LevelInfo)) slog.SetDefault(logger) } }