Skip to content

Commit

Permalink
Apollo: Release source code for 52.5
Browse files Browse the repository at this point in the history
  • Loading branch information
acrespo committed Dec 2, 2024
1 parent fed4aa1 commit a803f53
Show file tree
Hide file tree
Showing 19 changed files with 258 additions and 37 deletions.
23 changes: 21 additions & 2 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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<String>
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
46 changes: 46 additions & 0 deletions android/apollo/src/main/java/io/muun/apollo/data/os/NfcProvider.kt
Original file line number Diff line number Diff line change
@@ -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<Pair<Float, Float>> {
val result = mutableListOf<Pair<Float, Float>>()

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
}
}
11 changes: 11 additions & 0 deletions android/apollo/src/main/java/io/muun/apollo/data/os/OS.kt
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ object OS {
fun supportsKeystoreExceptionPublicMethods(): Boolean =
isAndroidTiramisuOrNewer()

fun supportsAvailableNfcAntennas(): Boolean =
isAndroidUpsideDownCakeOrNewer()


// PRIVATE STUFF:

/**
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -516,7 +516,8 @@ sealed class AnalyticsEvent(metadataKeyValues: List<Pair<String, Any>> = 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()
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -11,8 +20,8 @@ data class CrashReport(
val metadata: MutableMap<String, Serializable>,
) {

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()
Expand All @@ -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", " ")
Expand Down
Loading

0 comments on commit a803f53

Please sign in to comment.