Skip to content

Commit

Permalink
Apollo: Release source code for 51.10
Browse files Browse the repository at this point in the history
  • Loading branch information
acrespo committed May 17, 2024
1 parent 14e5b58 commit 7bff881
Show file tree
Hide file tree
Showing 46 changed files with 692 additions and 201 deletions.
20 changes: 20 additions & 0 deletions android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ follow [https://changelog.md/](https://changelog.md/) guidelines.

## [Unreleased]

## [51.10] - 2024-05-17

### ADDED

- Background notification processing reliability improvements

### CHANGED

- Made outpoints and utxoStatus available to Libwallet's PaymentAnalyzer. Which involved a client
data migration to init utxos' status.
- Enhanced crashes and error reports with extra metadata.
- Include swap_uuid in newop events for better lightning payments metrics.
- Notify logout upon security logout (e.g 3 incorrect pin attempts).

### FIXED

- Fixed ANRs happening when trying to send a email error report.
- Adjusted overly verbose logging in release.
- Fixed problems and crashes in devices where VES currency is not supported.

## [51.9] - 2024-04-30

- Background notification processing reliability improvements
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,36 @@ package io.muun.apollo.data.analytics
import android.content.Context
import android.content.res.Resources
import android.os.Bundle
import android.util.Log
import com.google.firebase.analytics.FirebaseAnalytics
import io.muun.apollo.domain.analytics.AnalyticsEvent
import io.muun.apollo.domain.model.report.CrashReport
import io.muun.apollo.domain.model.user.User
import rx.Single
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AnalyticsProvider @Inject constructor(val context: Context) {
class AnalyticsProvider @Inject constructor(context: Context) {

private val fba = FirebaseAnalytics.getInstance(context)

// Just for enriching error logs. A best effort to add metadata
private val inMemoryMapBreadcrumbCollector = sortedMapOf<Long, Bundle>()

fun loadBigQueryPseudoId(): Single<String?> =
Single.fromEmitter<String> { emitter ->
fba.appInstanceId
.addOnSuccessListener { id: String? ->
// id can be null on platforms without google play services.
Timber.d("Loaded BigQueryPseudoId: $id")
emitter.onSuccess(id)
}
.addOnFailureListener { error ->
emitter.onError(error)
}
}

/**
* Set the user's properties, to be used by Analytics.
*/
Expand All @@ -38,6 +52,12 @@ class AnalyticsProvider @Inject constructor(val context: Context) {
fun report(event: AnalyticsEvent) {
try {
actuallyReport(event)

// Avoid recursion (Timber.i reports a breadcrumb). TODO proper design and fix this
if (event !is AnalyticsEvent.E_BREADCRUMB) {
Timber.i("AnalyticsProvider", event.toString())
}

} catch (t: Throwable) {

val bundle = Bundle().apply { putString("event", event.eventId) }
Expand All @@ -60,7 +80,6 @@ class AnalyticsProvider @Inject constructor(val context: Context) {

fba.logEvent(event.eventId, bundle)
inMemoryMapBreadcrumbCollector[System.currentTimeMillis()] = bundle
Log.i("AnalyticsProvider", event.toString())
}

private fun getBreadcrumbMetadata(): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package io.muun.apollo.data.logging

import android.app.Application
import android.os.Build
import com.google.firebase.crashlytics.FirebaseCrashlytics
import io.muun.apollo.data.analytics.AnalyticsProvider
import io.muun.apollo.data.os.GooglePlayServicesHelper
import io.muun.apollo.data.os.OS
import io.muun.apollo.data.os.TelephonyInfoProvider
import io.muun.apollo.data.os.getInstallSourceInfo
import io.muun.apollo.domain.action.debug.ForceCrashReportAction
import io.muun.apollo.domain.analytics.Analytics
import io.muun.apollo.domain.analytics.AnalyticsEvent
import io.muun.apollo.domain.errors.fcm.FcmTokenNotAvailableError
import io.muun.apollo.domain.errors.newop.CyclicalSwapError
Expand All @@ -14,8 +18,10 @@ import io.muun.apollo.domain.errors.newop.InvoiceExpiresTooSoonException
import io.muun.apollo.domain.errors.newop.InvoiceMissingAmountException
import io.muun.apollo.domain.errors.newop.NoPaymentRouteException
import io.muun.apollo.domain.errors.newop.UnreachableNodeException
import io.muun.apollo.domain.model.InstallSourceInfo
import io.muun.apollo.domain.model.report.CrashReport
import io.muun.apollo.domain.utils.isInstanceOrIsCausedByError
import timber.log.Timber

object Crashlytics {

Expand All @@ -25,11 +31,39 @@ object Crashlytics {
null
}

private var analytics: Analytics? = null
private var analyticsProvider: AnalyticsProvider? = null

private var bigQueryPseudoId: String? = null

private var googlePlayServicesAvailable: Boolean? = null

private var installSource: InstallSourceInfo? = null

private var region: String? = null

private var defaultUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = null

@JvmStatic
fun init(application: Application) {
this.analytics = Analytics(AnalyticsProvider(application))
this.analyticsProvider = AnalyticsProvider(application)
this.analyticsProvider?.loadBigQueryPseudoId()
?.subscribe({ bigQueryPseudoId = it }, { Timber.e(it) })

this.googlePlayServicesAvailable = GooglePlayServicesHelper(application).isAvailable
this.installSource = application.getInstallSourceInfo()
this.region = TelephonyInfoProvider(application).region.orElse("null")

this.defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler(customUncaughtExceptionHandler)
}

// enhance crashlytics crashes with custom keys
private val customUncaughtExceptionHandler = Thread.UncaughtExceptionHandler { thread, ex ->

setStaticCustomKeys()

//call the default exception handler
this.defaultUncaughtExceptionHandler?.uncaughtException(thread, ex)
}

/**
Expand All @@ -48,7 +82,7 @@ object Crashlytics {
@Deprecated("Not really but you shouldn't use this directly. Use Timber.i(). See MuunTree.")
fun logBreadcrumb(breadcrumb: String) {
crashlytics?.log(breadcrumb)
analytics?.report(
analyticsProvider?.report(
AnalyticsEvent.E_BREADCRUMB(
breadcrumb
)
Expand All @@ -65,15 +99,17 @@ object Crashlytics {
return
}

// Note: these custom keys are associated with the non-fatal error being tracked but also
// with the subsequent crash if the error generates one (e.g if error isn't caught/handled).
crashlytics?.setCustomKey("tag", report.tag)
crashlytics?.setCustomKey("message", report.message)
crashlytics?.setCustomKey("locale", LoggingContext.locale)
setStaticCustomKeys()

for (entry in report.metadata.entries) {
crashlytics?.setCustomKey(entry.key, entry.value.toString())
}

analytics?.report(
analyticsProvider?.report(
AnalyticsEvent.E_CRASHLYTICS_ERROR(
report.error.javaClass.simpleName + ":" + report.error.localizedMessage
)
Expand All @@ -82,6 +118,27 @@ object Crashlytics {
crashlytics?.recordException(report.error)
}

private fun setStaticCustomKeys() {
crashlytics?.setCustomKey("locale", LoggingContext.locale)
crashlytics?.setCustomKey("region", region ?: "null")
crashlytics?.setCustomKey("bigQueryPseudoId", bigQueryPseudoId ?: "null")
crashlytics?.setCustomKey("abi", getSupportedAbi())
crashlytics?.setCustomKey("isPlayServicesAvailable", googlePlayServicesAvailable.toString())
crashlytics?.setCustomKey(
"installSource-installingPackage", installSource?.installingPackageName ?: "null"
)
crashlytics?.setCustomKey(
"installSource-initiatingPackage", installSource?.initiatingPackageName ?: "null"
)
}

private fun getSupportedAbi() =
if (OS.supportsSupportedAbis()) {
Build.SUPPORTED_ABIS[0]
} else {
"api19"
}

/**
* Send a "fallback" reporting error to Crashlytics. This means that there was an error while
* doing our usual error report processing. Hence we try to report the original error data (tag,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.muun.apollo.data.logging

import android.util.Log
import io.muun.apollo.data.logging.Crashlytics.logBreadcrumb
import io.muun.apollo.domain.model.report.CrashReportBuilder
import timber.log.Timber

Expand All @@ -13,20 +12,20 @@ class MuunTree : Timber.DebugTree() {
override fun log(priority: Int, tag: String?, message: String?, error: Throwable?) {
// For low priority logs, we don't have any special treatment:
if (priority < Log.INFO) {
super.log(priority, tag, message, error)
sendToLogcat(priority, tag, message, error)
return
}

when (priority) {
Log.INFO -> {
Log.i("Breadcrumb", message!!)
sendToLogcat(Log.INFO, "Breadcrumb", message!!, null)
@Suppress("DEPRECATION") // I know. These are the only allowed usages.
logBreadcrumb(message)
Crashlytics.logBreadcrumb(message)
}
Log.WARN -> {
Log.w(tag, message!!)
sendToLogcat(Log.WARN, tag, message!!, null)
@Suppress("DEPRECATION") // I know. These are the only allowed usages.
logBreadcrumb("Warning: $message")
Crashlytics.logBreadcrumb("Warning: $message")
}
else -> { // Log.ERROR && Log.ASSERT
sendCrashReport(tag, message, error)
Expand All @@ -50,9 +49,7 @@ class MuunTree : Timber.DebugTree() {
Crashlytics.reportError(report)
}

if (LoggingContext.sendToLogcat) {
Log.e(report.tag, "${report.message} ${report.metadata}", report.error)
}
sendToLogcat(Log.ERROR, report.tag, "${report.message} ${report.metadata}", report.error)
}

private fun sendFallbackCrashReport(
Expand All @@ -62,13 +59,17 @@ class MuunTree : Timber.DebugTree() {
crashReportingError: Throwable,
) {

if (LoggingContext.sendToLogcat) {
Log.e("CrashReport:$tag", "During error processing", crashReportingError)
Log.e("CrashReport:$tag", message, error)
}
sendToLogcat(Log.ERROR, "CrashReport:$tag", "During error processing", crashReportingError)
sendToLogcat(Log.ERROR, "CrashReport:$tag", message, error)

if (LoggingContext.sendToCrashlytics) {
Crashlytics.reportReportingError(tag, message, error, crashReportingError)
}
}

private fun sendToLogcat(priority: Int, tag: String?, message: String?, error: Throwable?) {
if (LoggingContext.sendToLogcat) {
super.log(priority, tag, message, error)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,19 @@ class ConnectivityInfoProvider @Inject constructor(context: Context) {

return if (isVpnNetworkAvailable) 2 else 3
}

val proxyHttp: String
get() {
return System.getProperty("http.proxyHost") ?: ""
}

val proxyHttps: String
get() {
return System.getProperty("https.proxyHost") ?: ""
}

val proxySocks: String
get() {
return System.getProperty("socks.proxyHost") ?: ""
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.muun.apollo.data.os.GooglePlayHelper;
import io.muun.apollo.data.os.GooglePlayServicesHelper;
import io.muun.apollo.data.os.HardwareCapabilitiesProvider;
import io.muun.apollo.data.os.OS_ExtensionsKt;
import io.muun.apollo.domain.errors.newop.CyclicalSwapError;
import io.muun.apollo.domain.errors.newop.InvalidInvoiceException;
import io.muun.apollo.domain.errors.newop.InvoiceAlreadyUsedException;
Expand Down Expand Up @@ -95,10 +96,15 @@
public class HoustonClient extends BaseClient<HoustonService> {

private final ModelObjectsMapper modelMapper;

private final ApiObjectsMapper apiMapper;

private final Context context;

private final HardwareCapabilitiesProvider hardwareCapabilitiesProvider;

private final GooglePlayServicesHelper googlePlayServicesHelper;

private final GooglePlayHelper googlePlayHelper;

/**
Expand Down Expand Up @@ -144,7 +150,7 @@ public Observable<CreateFirstSessionOk> createFirstSession(
hardwareCapabilitiesProvider.getAndroidId(),
hardwareCapabilitiesProvider.getSystemUsersInfo(),
hardwareCapabilitiesProvider.getDrmClientIds(),
HoustonClient_ExtensionsKt.getInstallSourceInfo(context),
OS_ExtensionsKt.getInstallSourceInfo(context),
hardwareCapabilitiesProvider.getBootCount(),
hardwareCapabilitiesProvider.getGlEsVersion(),
CpuInfoProvider.INSTANCE.getCpuInfo(),
Expand Down Expand Up @@ -174,7 +180,7 @@ public Observable<CreateSessionOk> createLoginSession(
hardwareCapabilitiesProvider.getAndroidId(),
hardwareCapabilitiesProvider.getSystemUsersInfo(),
hardwareCapabilitiesProvider.getDrmClientIds(),
HoustonClient_ExtensionsKt.getInstallSourceInfo(context),
OS_ExtensionsKt.getInstallSourceInfo(context),
hardwareCapabilitiesProvider.getBootCount(),
hardwareCapabilitiesProvider.getGlEsVersion(),
CpuInfoProvider.INSTANCE.getCpuInfo(),
Expand Down Expand Up @@ -204,7 +210,7 @@ public Observable<Challenge> createRcLoginSession(
hardwareCapabilitiesProvider.getAndroidId(),
hardwareCapabilitiesProvider.getSystemUsersInfo(),
hardwareCapabilitiesProvider.getDrmClientIds(),
HoustonClient_ExtensionsKt.getInstallSourceInfo(context),
OS_ExtensionsKt.getInstallSourceInfo(context),
hardwareCapabilitiesProvider.getBootCount(),
hardwareCapabilitiesProvider.getGlEsVersion(),
CpuInfoProvider.INSTANCE.getCpuInfo(),
Expand Down Expand Up @@ -347,6 +353,13 @@ public Observable<Void> notifyLogout(String authHeader) {
return getService().notifyLogout(authHeader);
}

/**
* [Only works for "Multiple sessions" users] Expire all user sessions except the current one.
*/
public Completable expireAllOtherSessions() {
return getService().expireAllOtherSessions();
}

/**
* Updates the GCM token for the current user.
*/
Expand All @@ -359,7 +372,8 @@ public Observable<Void> updateFcmToken(@NotNull String fcmToken) {
* not return all the existing notifications, it's up to the caller to make subsequent calls.
*/
public Observable<NotificationReport> fetchNotificationReportAfter(
@Nullable Long notificationId) {
@Nullable Long notificationId
) {

return getService().fetchNotificationReportAfter(notificationId)
.map(modelMapper::mapNotificationReport);
Expand All @@ -372,7 +386,8 @@ public Observable<Void> confirmNotificationsDeliveryUntil(
final long notificationId,
final String deviceModel,
final String osVersion,
final String appStatus) {
final String appStatus
) {

return getService().confirmNotificationsDeliveryUntil(
notificationId, deviceModel, osVersion, appStatus
Expand Down Expand Up @@ -741,8 +756,10 @@ public Single<IncomingSwapFulfillmentData> fetchFulfillmentData(final String inc
/**
* Push the fulfillment TX for an incoming swap.
*/
public Completable pushFulfillmentTransaction(final String incomingSwap,
final RawTransaction rawTransaction) {
public Completable pushFulfillmentTransaction(
final String incomingSwap,
final RawTransaction rawTransaction
) {

return getService().pushFulfillmentTransaction(incomingSwap, rawTransaction);
}
Expand Down
Loading

0 comments on commit 7bff881

Please sign in to comment.