From 7d0d67f8ea299e55bf29dceb9d74c6d6d83a4c9b Mon Sep 17 00:00:00 2001 From: acrespo Date: Wed, 17 Jan 2024 21:10:08 -0300 Subject: [PATCH] Apollo: Release source code for 51.6 --- android/CHANGELOG.md | 10 ++ .../apollo/data/net/ApiObjectsMapper.java | 23 ++++- .../base/interceptor/AuthHeaderInterceptor.kt | 16 ++-- .../interceptor/LanguageHeaderInterceptor.kt | 4 +- .../preferences/BackgroundTimesRepository.kt | 95 +++++++++++++++++++ .../data/preferences/RepositoryRegistry.kt | 3 +- .../apollo/domain/BackgroundTimesService.kt | 28 ++++++ .../operation/ResolveLnInvoiceAction.kt | 48 ++++++---- .../operation/ResolveOperationUriAction.kt | 14 ++- .../apollo/domain/model/BackgroundEvent.kt | 6 ++ .../apollo/domain/model/PreparedPayment.java | 26 ----- .../domain/model/SubmarineSwapRequest.java | 18 +++- .../domain/action/OperationActionsTest.java | 18 +++- android/apolloui/build.gradle | 4 +- android/apolloui/proguard/proguard-apollo.pro | 1 + android/apolloui/src/main/AndroidManifest.xml | 13 ++- .../presentation/app/ApolloApplication.java | 6 ++ .../ui/new_operation/NewOperationPresenter.kt | 2 +- .../muun/common/api/BackgroundEventJson.java | 33 +++++++ .../common/api/SubmarineSwapRequestJson.java | 17 +++- 20 files changed, 312 insertions(+), 73 deletions(-) create mode 100644 android/apollo/src/main/java/io/muun/apollo/data/preferences/BackgroundTimesRepository.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/BackgroundTimesService.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/model/BackgroundEvent.kt create mode 100644 common/src/main/java/io/muun/common/api/BackgroundEventJson.java diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index c531ade2..7f384e08 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -6,6 +6,16 @@ follow [https://changelog.md/](https://changelog.md/) guidelines. ## [Unreleased] +## [51.6] - 2024-01-17 + +### FIXED + +- A problem regarding background/foreground event tracking + +### CHANGED + +- Enhanced reliability of certain specific swaps' execution + ## [51.5] - 2023-12-22 ### FIXED diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/ApiObjectsMapper.java b/android/apollo/src/main/java/io/muun/apollo/data/net/ApiObjectsMapper.java index 31ba1cbf..273ae331 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/net/ApiObjectsMapper.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/ApiObjectsMapper.java @@ -6,6 +6,7 @@ import io.muun.apollo.data.os.GooglePlayServicesHelper; import io.muun.apollo.data.serialization.dates.ApolloZonedDateTime; import io.muun.apollo.domain.libwallet.Invoice; +import io.muun.apollo.domain.model.BackgroundEvent; import io.muun.apollo.domain.model.BitcoinAmount; import io.muun.apollo.domain.model.EmergencyKitExport; import io.muun.apollo.domain.model.IncomingSwapFulfillmentData; @@ -17,6 +18,7 @@ import io.muun.apollo.domain.model.SystemUserInfo; import io.muun.apollo.domain.model.user.UserProfile; import io.muun.common.api.AndroidSystemUserInfoJson; +import io.muun.common.api.BackgroundEventJson; import io.muun.common.api.BitcoinAmountJson; import io.muun.common.api.ChallengeKeyJson; import io.muun.common.api.ChallengeSetupJson; @@ -508,7 +510,26 @@ public FeedbackJson mapFeedback(String content) { * Create a Submarine Swap Request. */ public SubmarineSwapRequestJson mapSubmarineSwapRequest(SubmarineSwapRequest request) { - return new SubmarineSwapRequestJson(request.invoice, request.swapExpirationInBlocks); + return new SubmarineSwapRequestJson( + request.invoice, + request.swapExpirationInBlocks, + request.origin.name().toLowerCase(Locale.getDefault()), // match analytics event + mapBackgroundTimes(request.bkgTimes) + ); + } + + private List mapBackgroundTimes(List bkgTimes) { + + final List result = new ArrayList<>(); + + for (BackgroundEvent bkgTime : bkgTimes) { + result.add(new BackgroundEventJson( + bkgTime.getBeginTimeInMillis(), + bkgTime.getDurationInMillis() + )); + } + + return result; } /** diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/AuthHeaderInterceptor.kt b/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/AuthHeaderInterceptor.kt index b9e098f6..34d39b6c 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/AuthHeaderInterceptor.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/AuthHeaderInterceptor.kt @@ -12,38 +12,38 @@ class AuthHeaderInterceptor @Inject constructor( private val authRepository: AuthRepository, ) : BaseInterceptor() { - override fun processRequest(request: Request): Request { + override fun processRequest(originalRequest: Request): Request { // Attach the request header if a token is available: val serverJwt = authRepository.serverJwt.orElse(null) return if (serverJwt != null) { - request.newBuilder() + originalRequest.newBuilder() .addHeader(HeaderUtils.AUTHORIZATION, "Bearer $serverJwt") .build() } else { - request + originalRequest } } - override fun processResponse(response: Response): Response { + override fun processResponse(originalResponse: Response): Response { // Save the token in the response header if one is found - val authHeaderValue = response.header(HeaderUtils.AUTHORIZATION) + val authHeaderValue = originalResponse.header(HeaderUtils.AUTHORIZATION) HeaderUtils.getBearerTokenFromHeader(authHeaderValue) .ifPresent { serverJwt: String -> authRepository.storeServerJwt(serverJwt) } // We need a reliable way (across all envs: local, CI, prd, etc...) to identify the logout // requests. We could inject HoustonConfig and build the entire URL (minus port) // or... we can do this :) - val url = response.request().url().url().toString() + val url = originalResponse.request().url().url().toString() val isLogout = url.endsWith("sessions/logout") if (!isLogout) { - val sessionStatusHeaderValue = response.header(HeaderUtils.SESSION_STATUS) + val sessionStatusHeaderValue = originalResponse.header(HeaderUtils.SESSION_STATUS) HeaderUtils.getSessionStatusFromHeader(sessionStatusHeaderValue) .ifPresent { sessionStatus: SessionStatus -> authRepository.storeSessionStatus(sessionStatus) } } - return response + return originalResponse } } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/LanguageHeaderInterceptor.kt b/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/LanguageHeaderInterceptor.kt index 6dd3aff2..e0560b1a 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/LanguageHeaderInterceptor.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/base/interceptor/LanguageHeaderInterceptor.kt @@ -11,9 +11,9 @@ class LanguageHeaderInterceptor @Inject constructor( private val applicationContext: Context, ) : BaseInterceptor() { - override fun processRequest(request: Request): Request { + override fun processRequest(originalRequest: Request): Request { val language = applicationContext.locale().language - return request.newBuilder() + return originalRequest.newBuilder() .addHeader( HeaderUtils.CLIENT_LANGUAGE, language.ifEmpty { HeaderUtils.DEFAULT_LANGUAGE_VALUE } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/preferences/BackgroundTimesRepository.kt b/android/apollo/src/main/java/io/muun/apollo/data/preferences/BackgroundTimesRepository.kt new file mode 100644 index 00000000..3c72d149 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/data/preferences/BackgroundTimesRepository.kt @@ -0,0 +1,95 @@ +package io.muun.apollo.data.preferences + +import android.content.Context +import io.muun.apollo.data.preferences.adapter.JsonListPreferenceAdapter +import io.muun.apollo.data.preferences.rx.Preference +import io.muun.apollo.domain.model.BackgroundEvent +import javax.inject.Inject + +class BackgroundTimesRepository @Inject constructor( + context: Context, + repositoryRegistry: RepositoryRegistry, +) : BaseRepository(context, repositoryRegistry) { + + companion object { + private const val BACKGROUND_TIMES_KEY = "background_times_key" + private const val LAST_BACKGROUND_BEGIN_TIME_KEY = "last_background_begin_time_key" + } + + private class StoredBackgroundEvent { + var beginTimeInMillis: Long = 0 + var durationInMillis: Long = 0 + + /** + * Constructor from the model. + */ + constructor(bkgEvent: BackgroundEvent) { + beginTimeInMillis = bkgEvent.beginTimeInMillis + durationInMillis = bkgEvent.durationInMillis + + } + + /** + * JSON constructor. + */ + @Suppress("unused") + constructor() + + fun toModel(): BackgroundEvent { + return BackgroundEvent( + beginTimeInMillis, + durationInMillis + ) + } + } + + override fun getFileName(): String = + "background_times" + + private val lastBackgroundBeginTimePreference: Preference = + rxSharedPreferences.getLong(LAST_BACKGROUND_BEGIN_TIME_KEY, null) + + private val backgroundTimesPreferences: Preference> = + rxSharedPreferences.getObject( + BACKGROUND_TIMES_KEY, + emptyList(), + JsonListPreferenceAdapter(StoredBackgroundEvent::class.java) + ) + + fun recordEnterBackground() { + lastBackgroundBeginTimePreference.set(System.currentTimeMillis()) + } + + fun getLastBackgroundBeginTime(): Long? { + return lastBackgroundBeginTimePreference.get() + } + + fun recordBackgroundEvent(bkgBeginTime: Long, duration: Long) { + val storedBkgTimes = getBackgroundTimes() + val bkgTimes = storedBkgTimes.toMutableList() + + bkgTimes.add(BackgroundEvent(bkgBeginTime, duration)) + + storeBkgTimes(bkgTimes) + lastBackgroundBeginTimePreference.set(null) + } + + fun getBackgroundTimes(): List { + return backgroundTimesPreferences.get()!!.map { it.toModel() } + } + + fun pruneIfGreaterThan(maxBkgTimesArraySize: Int) { + val storedBkgTimes = getBackgroundTimes() + val bkgTimes = storedBkgTimes.takeLast(maxBkgTimesArraySize) + + storeBkgTimes(bkgTimes) + } + + private fun storeBkgTimes(bkgTimes: List) { + val storedBkgTimes = bkgTimes.map { it.toJson() } + backgroundTimesPreferences.set(storedBkgTimes) + } + + private fun BackgroundEvent.toJson(): StoredBackgroundEvent = + StoredBackgroundEvent(this) +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/preferences/RepositoryRegistry.kt b/android/apollo/src/main/java/io/muun/apollo/data/preferences/RepositoryRegistry.kt index 1e5a3428..45f95fd2 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/preferences/RepositoryRegistry.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/preferences/RepositoryRegistry.kt @@ -48,7 +48,8 @@ class RepositoryRegistry { PlayIntegrityNonceRepository::class.java, NotificationPermissionStateRepository::class.java, NotificationPermissionDeniedRepository::class.java, - NotificationPermissionSkippedRepository::class.java + NotificationPermissionSkippedRepository::class.java, + BackgroundTimesRepository::class.java ) // Notable exceptions: diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/BackgroundTimesService.kt b/android/apollo/src/main/java/io/muun/apollo/domain/BackgroundTimesService.kt new file mode 100644 index 00000000..c5bae59b --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/BackgroundTimesService.kt @@ -0,0 +1,28 @@ +package io.muun.apollo.domain + +import io.muun.apollo.data.preferences.BackgroundTimesRepository +import javax.inject.Inject + +class BackgroundTimesService @Inject constructor( + private val backgroundTimesRepository: BackgroundTimesRepository, +) { + + private val MAX_BKG_TIMES_ARRAY_SIZE: Int = 100 + + fun enterBackground() { + backgroundTimesRepository.recordEnterBackground() + } + + fun enterForeground() { + backgroundTimesRepository.pruneIfGreaterThan(MAX_BKG_TIMES_ARRAY_SIZE) + + val backgroundBeginTime = backgroundTimesRepository.getLastBackgroundBeginTime() + @Suppress("FoldInitializerAndIfToElvis") + if (backgroundBeginTime == null) { + return + } + + val duration = System.currentTimeMillis() - backgroundBeginTime + backgroundTimesRepository.recordBackgroundEvent(backgroundBeginTime, duration) + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt index c59495ae..07b84ad6 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveLnInvoiceAction.kt @@ -2,9 +2,11 @@ package io.muun.apollo.domain.action.operation import androidx.annotation.VisibleForTesting import io.muun.apollo.data.net.HoustonClient +import io.muun.apollo.data.preferences.BackgroundTimesRepository import io.muun.apollo.data.preferences.FeeWindowRepository import io.muun.apollo.data.preferences.KeysRepository -import io.muun.apollo.domain.action.base.BaseAsyncAction1 +import io.muun.apollo.domain.action.base.BaseAsyncAction2 +import io.muun.apollo.domain.analytics.NewOperationOrigin import io.muun.apollo.domain.errors.newop.InvalidSwapException import io.muun.apollo.domain.errors.newop.InvoiceExpiredException import io.muun.apollo.domain.libwallet.DecodedInvoice @@ -45,41 +47,55 @@ import javax.inject.Inject import javax.inject.Singleton import javax.money.MonetaryAmount - @Singleton class ResolveLnInvoiceAction @Inject internal constructor( private val network: NetworkParameters, private val houstonClient: HoustonClient, private val keysRepository: KeysRepository, private val feeWindowRepository: FeeWindowRepository, -) : BaseAsyncAction1() { + private val backgroundTimesRepository: BackgroundTimesRepository +) : BaseAsyncAction2() { companion object { private const val BLOCKS_IN_A_DAY = 24 * 6 // this is 144 private const val DAYS_IN_A_WEEK = 7 } - override fun action(rawInvoice: String): Observable = + override fun action( + rawInvoice: String, + origin: NewOperationOrigin, + ): Observable = Observable.defer { - resolveLnUri(rawInvoice) + resolveLnUri(rawInvoice, origin) } - private fun resolveLnUri(rawInvoice: String): Observable { + private fun resolveLnUri( + rawInvoice: String, + origin: NewOperationOrigin, + ): Observable { val invoice = decodeInvoice(network, rawInvoice) if (invoice.expirationTime.isBefore(DateUtils.now())) { throw InvoiceExpiredException(invoice.original) } - return prepareSwap(buildSubmarineSwapRequest(invoice)) + return prepareSwap(buildSubmarineSwapRequest(invoice, origin)) .map { swap: SubmarineSwap -> buildPaymentRequest(invoice, swap) } } - private fun buildSubmarineSwapRequest(invoice: DecodedInvoice): SubmarineSwapRequest { + private fun buildSubmarineSwapRequest( + invoice: DecodedInvoice, + origin: NewOperationOrigin, + ): SubmarineSwapRequest { // We used to care a lot about this number for v1 swaps since it was the refund time // With swaps v2 we have collaborative refunds so we don't quite care and go for the max val swapExpirationInBlocks = BLOCKS_IN_A_DAY * DAYS_IN_A_WEEK - return SubmarineSwapRequest(invoice.original, swapExpirationInBlocks) + return SubmarineSwapRequest( + invoice.original, + swapExpirationInBlocks, + origin, + backgroundTimesRepository.getBackgroundTimes() + ) } private fun buildPaymentRequest(invoice: DecodedInvoice, swap: SubmarineSwap): PaymentRequest { @@ -161,7 +177,7 @@ class ResolveLnInvoiceAction @Inject internal constructor( * Validate Submarine Swap Server response. The end goal is to verify that the redeem script * returned by the server is the script that is actually encoded in the reported swap address. */ - fun validateSwap( + private fun validateSwap( originalInvoice: String, originalExpirationInBlocks: Int, userPublicKeyPair: PublicKeyPair, @@ -216,15 +232,15 @@ class ResolveLnInvoiceAction @Inject internal constructor( // Check that the witness script was computed according to the given parameters val witnessScript = createWitnessScript( Encodings.hexToBytes(paymentHashInHex), - userPublicKey.getPublicKeyBytes(), - muunPublicKey.getPublicKeyBytes(), + userPublicKey.publicKeyBytes, + muunPublicKey.publicKeyBytes, Encodings.hexToBytes(fundingOutput.serverPublicKeyInHex), fundingOutput.expirationInBlocks!!.toLong() ) // Check that the script hashes to the output address we'll be using val outputAddress: Address = createAddress(network, witnessScript) - if (!outputAddress.toString().equals(fundingOutput.outputAddress)) { + if (outputAddress.toString() != fundingOutput.outputAddress) { return false } @@ -243,7 +259,7 @@ class ResolveLnInvoiceAction @Inject internal constructor( /** * Create the witness script for spending the submarine swap output. */ - fun createWitnessScript( + private fun createWitnessScript( swapPaymentHash256: ByteArray?, userPublicKey: ByteArray?, muunPublicKey: ByteArray?, @@ -312,13 +328,13 @@ class ResolveLnInvoiceAction @Inject internal constructor( .op(OP_CHECKSIG) .op(OP_ENDIF) .build() - .getProgram() + .program } /** * Create an address. */ - fun createAddress(network: NetworkParameters?, witnessScript: ByteArray?): Address { + private fun createAddress(network: NetworkParameters?, witnessScript: ByteArray?): Address { val witnessScriptHash: ByteArray = Sha256Hash.hash(witnessScript) return SegwitAddress.fromHash(network, witnessScriptHash) } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveOperationUriAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveOperationUriAction.kt index 0930466f..ce68ea5b 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveOperationUriAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/ResolveOperationUriAction.kt @@ -1,12 +1,11 @@ package io.muun.apollo.domain.action.operation -import io.muun.apollo.domain.action.base.BaseAsyncAction1 +import io.muun.apollo.domain.action.base.BaseAsyncAction2 +import io.muun.apollo.domain.analytics.NewOperationOrigin import io.muun.apollo.domain.model.OperationUri import io.muun.apollo.domain.model.PaymentRequest import io.muun.common.exception.MissingCaseError - import rx.Observable - import javax.inject.Inject import javax.inject.Singleton @@ -17,18 +16,17 @@ import javax.inject.Singleton class ResolveOperationUriAction @Inject constructor( private val resolveBitcoinUri: ResolveBitcoinUriAction, private val resolveMuunUri: ResolveMuunUriAction, - private val resolveLnInvoice: ResolveLnInvoiceAction - -): BaseAsyncAction1() { + private val resolveLnInvoice: ResolveLnInvoiceAction, +) : BaseAsyncAction2() { - override fun action(uri: OperationUri): Observable { + override fun action(uri: OperationUri, origin: NewOperationOrigin): Observable { return Observable.defer { when { // First, check if this is an internal Muun URI (contact or hardware wallet): uri.isMuun -> resolveMuunUri.action(uri) // Second, if the URI has a LN invoice, prioritize it: - uri.lnInvoice.isPresent -> resolveLnInvoice.action(uri.lnInvoice.get()) + uri.lnInvoice.isPresent -> resolveLnInvoice.action(uri.lnInvoice.get(), origin) // Third, try looking for a Bitcoin address (BIP21 or BIP72): uri.bitcoinAddress.isPresent || uri.asyncUrl.isPresent -> diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/BackgroundEvent.kt b/android/apollo/src/main/java/io/muun/apollo/domain/model/BackgroundEvent.kt new file mode 100644 index 00000000..0daf9c53 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/BackgroundEvent.kt @@ -0,0 +1,6 @@ +package io.muun.apollo.domain.model + +class BackgroundEvent( + val beginTimeInMillis: Long, + val durationInMillis: Long, +) \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/PreparedPayment.java b/android/apollo/src/main/java/io/muun/apollo/domain/model/PreparedPayment.java index cf0b7636..a5afc523 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/PreparedPayment.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/PreparedPayment.java @@ -19,9 +19,6 @@ public class PreparedPayment { public final List outpoints; - // TODO this should be removed - public PaymentRequest payReq; - public final PaymentRequest.Type type; public final Contact contact; @@ -30,29 +27,6 @@ public class PreparedPayment { public final SubmarineSwap swap; - /** - * Manual constructor. - */ - public PreparedPayment( - final BitcoinAmount amount, - final BitcoinAmount fee, - final String description, - final Long rateWindowHid, - final List outpoints, - final PaymentRequest payReq) { - - this.amount = amount; - this.fee = fee; - this.description = description; - this.rateWindowHid = rateWindowHid; - this.outpoints = outpoints; - this.payReq = payReq; - this.type = payReq.getType(); - this.contact = payReq.getContact(); - this.address = payReq.getAddress(); - this.swap = payReq.getSwap(); - } - /** * Manual constructor. */ diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/SubmarineSwapRequest.java b/android/apollo/src/main/java/io/muun/apollo/domain/model/SubmarineSwapRequest.java index 3e22c843..b61288aa 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/SubmarineSwapRequest.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/SubmarineSwapRequest.java @@ -1,5 +1,8 @@ package io.muun.apollo.domain.model; +import io.muun.apollo.domain.analytics.NewOperationOrigin; + +import java.util.List; import javax.validation.constraints.NotNull; public class SubmarineSwapRequest { @@ -10,6 +13,12 @@ public class SubmarineSwapRequest { @NotNull public Integer swapExpirationInBlocks; + @NotNull + public NewOperationOrigin origin; + + @NotNull + public List bkgTimes; + /** * Json constructor. */ @@ -19,9 +28,16 @@ public SubmarineSwapRequest() { /** * Manual constructor. */ - public SubmarineSwapRequest(String invoice, int swapExpirationInBlocks) { + public SubmarineSwapRequest( + String invoice, + int swapExpirationInBlocks, + NewOperationOrigin origin, + List bkgTimes + ) { this.invoice = invoice; this.swapExpirationInBlocks = swapExpirationInBlocks; + this.origin = origin; + this.bkgTimes = bkgTimes; } } diff --git a/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java b/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java index 1108aad1..52f27161 100644 --- a/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java +++ b/android/apollo/src/test/java/io/muun/apollo/domain/action/OperationActionsTest.java @@ -130,12 +130,17 @@ public void buildPaymentToContact() { doReturn(contactAddress) .when(contactActions).getAddressForContact(contact); - final PreparedPayment preparedPayment = new PreparedPayment(null, + final PreparedPayment preparedPayment = new PreparedPayment( null, null, null, null, - payReq); + null, + PaymentRequest.Type.TO_CONTACT, + contact, + null, + null + ); final Operation operation = submitPaymentAction.buildOperation(preparedPayment); @@ -177,12 +182,17 @@ public void buildPaymentToAddress() { final long someFee = 123456; - final PreparedPayment preparedPayment = new PreparedPayment(null, + final PreparedPayment preparedPayment = new PreparedPayment( null, null, null, null, - payReq); + null, + PaymentRequest.Type.TO_ADDRESS, + null, + payReq.getAddress(), + null + ); final Operation operation = submitPaymentAction.buildOperation(preparedPayment); // check direction diff --git a/android/apolloui/build.gradle b/android/apolloui/build.gradle index 6d5f88a1..5f216e76 100644 --- a/android/apolloui/build.gradle +++ b/android/apolloui/build.gradle @@ -91,8 +91,8 @@ android { applicationId "io.muun.apollo" minSdkVersion 19 targetSdkVersion 33 - versionCode 1105 - versionName "51.5" + versionCode 1106 + versionName "51.6" // 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/proguard/proguard-apollo.pro b/android/apolloui/proguard/proguard-apollo.pro index d08c8da6..13aa006f 100644 --- a/android/apolloui/proguard/proguard-apollo.pro +++ b/android/apolloui/proguard/proguard-apollo.pro @@ -49,3 +49,4 @@ # Don't mangle classes user for serialization -keep class io.muun.apollo.data.preferences.ForwardingPoliciesRepository$StoredForwardingPolicy { *; } +-keep class io.muun.apollo.data.preferences.BackgroundTimesRepository$StoredBackgroundEvent { *; } diff --git a/android/apolloui/src/main/AndroidManifest.xml b/android/apolloui/src/main/AndroidManifest.xml index 24aba82a..9b86b559 100644 --- a/android/apolloui/src/main/AndroidManifest.xml +++ b/android/apolloui/src/main/AndroidManifest.xml @@ -421,12 +421,21 @@ Since WorkManager 2.6, App Startup is used internally within WorkManager. To provide a custom initializer you need to remove the androidx.startup node. See: https://developer.android.com/topic/libraries/app-startup + + UPDATE: We're using ProcessLifecycleOwner. WorkManager and ProcessLifecycleOwner both use + androidx.startup. So we need to disable the default initializer for WorkManager without + disabling androidx.startup altogether. See: https://stackoverflow.com/a/74902862/901465 --> - + android:exported="false" + tools:node="merge"> + + diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/app/ApolloApplication.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/app/ApolloApplication.java index 093ab5c8..8ed9b197 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/app/ApolloApplication.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/app/ApolloApplication.java @@ -16,6 +16,7 @@ import io.muun.apollo.data.preferences.UserRepository; import io.muun.apollo.data.preferences.migration.PreferencesMigrationManager; import io.muun.apollo.domain.ApplicationLockManager; +import io.muun.apollo.domain.BackgroundTimesService; import io.muun.apollo.domain.NightModeManager; import io.muun.apollo.domain.action.session.DetectAppUpdateAction; import io.muun.apollo.domain.analytics.Analytics; @@ -86,6 +87,9 @@ public abstract class ApolloApplication extends Application @Inject FirebaseInstallationIdRepository firebaseInstallationIdRepository; + @Inject + BackgroundTimesService backgroundTimesService; + @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); @@ -335,11 +339,13 @@ public void onTerminate() { @Override public void onStart(@NonNull LifecycleOwner owner) { // app moved to foreground analytics.report(new AnalyticsEvent.E_APP_WILL_ENTER_FOREGROUND()); + backgroundTimesService.enterForeground(); } @Override public void onStop(@NonNull LifecycleOwner owner) { // app moved to background analytics.report(new AnalyticsEvent.E_APP_WILL_GO_TO_BACKGROUND()); + backgroundTimesService.enterBackground(); } @Override diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt index c5681824..f1d4743e 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationPresenter.kt @@ -473,7 +473,7 @@ class NewOperationPresenter @Inject constructor( // This is still needed because we need to: // - resolveLnInvoice for submarine swaps TODO mv this to libwallet // - resolveMuunUri for P2P/Contacts legacy feature TODO refactor this? - resolveOperationUriAction.run(OperationUri.fromString(uri)) + resolveOperationUriAction.run(OperationUri.fromString(uri), origin) view.setInitialBitcoinUnit(bitcoinUnitSel.get()) diff --git a/common/src/main/java/io/muun/common/api/BackgroundEventJson.java b/common/src/main/java/io/muun/common/api/BackgroundEventJson.java new file mode 100644 index 00000000..eeea4fc5 --- /dev/null +++ b/common/src/main/java/io/muun/common/api/BackgroundEventJson.java @@ -0,0 +1,33 @@ +package io.muun.common.api; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.NotNull; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonIgnoreProperties(ignoreUnknown = true) +public class BackgroundEventJson { + + @NotEmpty + public Long beginTimestampInMillis; + + @NotNull + public Long durationInMillis; + + + /** + * Json constructor. + */ + public BackgroundEventJson() { + } + + /** + * Manual constructor. + */ + public BackgroundEventJson(Long beginTimestampInMillis, Long durationInMillis) { + this.beginTimestampInMillis = beginTimestampInMillis; + this.durationInMillis = durationInMillis; + } +} diff --git a/common/src/main/java/io/muun/common/api/SubmarineSwapRequestJson.java b/common/src/main/java/io/muun/common/api/SubmarineSwapRequestJson.java index 155cd73d..e2a1218f 100644 --- a/common/src/main/java/io/muun/common/api/SubmarineSwapRequestJson.java +++ b/common/src/main/java/io/muun/common/api/SubmarineSwapRequestJson.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonInclude; import org.hibernate.validator.constraints.NotEmpty; +import java.util.List; +import javax.annotation.Nullable; import javax.validation.constraints.NotNull; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -16,6 +18,12 @@ public class SubmarineSwapRequestJson { @NotNull public Integer swapExpirationInBlocks; + @Nullable // For retrocompat endpoint + public String origin; + + @Nullable // For retrocompat endpoint + public List bkgTimes; + /** * Json constructor. */ @@ -25,8 +33,15 @@ public SubmarineSwapRequestJson() { /** * Manual constructor. */ - public SubmarineSwapRequestJson(String invoice, int swapExpirationInBlocks) { + public SubmarineSwapRequestJson( + String invoice, + int swapExpirationInBlocks, + @Nullable String origin, + @Nullable List bkgTimes + ) { this.invoice = invoice; this.swapExpirationInBlocks = swapExpirationInBlocks; + this.origin = origin; + this.bkgTimes = bkgTimes; } }