Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apollo: Release source code for 51.10 #149

Merged
merged 1 commit into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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