From f79f92076e33813839a3cdf9641e937dbbec7c10 Mon Sep 17 00:00:00 2001 From: acrespo Date: Wed, 15 Jan 2025 12:12:10 -0300 Subject: [PATCH] Apollo: Release source code for 52.6 --- android/CHANGELOG.md | 25 + android/apollo/build.gradle | 23 +- android/apollo/lint-baseline.xml | 527 +- android/apollo/src/main/AndroidManifest.xml | 2 +- .../io/muun/apollo/data/apis/DriveImpl.kt | 6 +- .../io/muun/apollo/data/di/DataComponent.java | 3 + .../io/muun/apollo/data/di/DataModule.java | 21 +- .../muun/apollo/data/logging/Crashlytics.kt | 27 +- .../io/muun/apollo/data/logging/MuunTree.kt | 34 +- .../apollo/data/net/ApiObjectsMapper.java | 56 +- .../data/net/ConnectivityInfoProvider.kt | 50 +- .../muun/apollo/data/net/HoustonClient.java | 16 +- .../data/os/ActivityManagerInfoProvider.kt | 77 +- .../os/BackgroundExecutionMetricsProvider.kt | 14 +- .../java/io/muun/apollo/data/os/Constants.kt | 9 +- .../muun/apollo/data/os/FileInfoProvider.kt | 25 +- .../data/os/HardwareCapabilitiesProvider.kt | 26 + .../io/muun/apollo/data/os/NfcProvider.kt | 38 + .../main/java/io/muun/apollo/data/os/OS.kt | 48 + .../data/os/PackageManagerInfoProvider.kt | 4 +- .../data/os/SystemCapabilitiesProvider.kt | 7 +- .../apollo/data/os/TelephonyInfoProvider.kt | 10 - .../muun/apollo/domain/EmailReportManager.kt | 6 + .../domain/action/NotificationActions.kt | 20 + .../action/base/CombineLatestAsyncAction.kt | 29 +- .../domain/action/di/ActionComponent.java | 4 +- .../operation/CreateOperationAction.java | 54 +- .../operation/UpdateOperationAction.java | 43 +- .../realtime/FetchRealTimeDataAction.java | 9 +- .../realtime/FetchRealTimeFeesAction.kt | 70 - .../action/realtime/PreloadFeeDataAction.kt | 108 + .../action/session/UseMuunLinkAction.kt | 4 +- .../apollo/domain/analytics/AnalyticsEvent.kt | 1 - .../domain/libwallet/GoLibwalletService.kt | 23 + .../domain/libwallet/LibwalletService.kt | 8 + .../domain/model/NextTransactionSize.java | 16 + .../apollo/domain/model/report/CrashReport.kt | 1 - .../domain/model/report/CrashReportBuilder.kt | 6 +- .../apollo/domain/model/report/EmailReport.kt | 44 +- .../muun/apollo/domain/sync/FeeDataSyncer.kt | 111 + .../domain/action/PreloadFeeDataActionTest.kt | 105 + .../apollo/domain/sync/FeeDataSyncerTest.kt | 149 + android/apolloui/build.gradle | 22 +- android/apolloui/lint-baseline.xml | 4603 ++++++++--------- .../apolloui/proguard/proguard-android.pro | 9 +- android/apolloui/src/main/AndroidManifest.xml | 2 +- .../presentation/app/ApolloApplication.java | 18 + .../ui/activity/extension/MuunDialog.kt | 20 + .../presentation/ui/base/BaseActivity.java | 1 + .../presentation/ui/home/HomeActivity.java | 1 + .../ui/new_operation/NewOperationPresenter.kt | 31 +- .../edit_username/EditUsernameActivity.java | 7 +- .../src/main/res/layout/home_activity.xml | 1 - build.gradle | 9 +- .../java/io/muun/common/api/ClientJson.java | 19 +- .../common/crypto/hd/DerivationPathUtils.java | 7 +- .../common/crypto/hd/PublicKeyTriple.java | 24 +- .../io/muun/common/model/SizeForAmount.java | 4 +- .../java/io/muun/common/utils/LnInvoice.java | 2 + .../io/muun/common/utils/Preconditions.java | 31 +- .../crypto/hd/DerivationPathUtilsTest.java | 63 + .../io/muun/common/utils/LnInvoiceTest.java | 7 +- gradle.properties | 7 + gradle/wrapper/gradle-wrapper.properties | 2 +- libwallet/aescbc/aescbc.go | 10 +- libwallet/newop/state.go | 9 +- libwallet/operation/fees.go | 5 +- linters/findbugs/check-android.gradle | 4 +- linters/findbugs/check.gradle | 4 +- linters/pmd/check-android.gradle | 4 +- linters/pmd/check.gradle | 4 +- settings.gradle | 12 + 72 files changed, 3974 insertions(+), 2827 deletions(-) delete mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeFeesAction.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/PreloadFeeDataAction.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/libwallet/GoLibwalletService.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletService.kt create mode 100644 android/apollo/src/main/java/io/muun/apollo/domain/sync/FeeDataSyncer.kt create mode 100644 android/apollo/src/test/java/io/muun/apollo/domain/action/PreloadFeeDataActionTest.kt create mode 100644 android/apollo/src/test/java/io/muun/apollo/domain/sync/FeeDataSyncerTest.kt create mode 100644 common/src/test/java/io/muun/common/crypto/hd/DerivationPathUtilsTest.java diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 39748186..2a5757ef 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -6,6 +6,29 @@ follow [https://changelog.md/](https://changelog.md/) guidelines. ## [Unreleased] +## [52.6] - 2025-01-14 + +### ADDED + +- Background notification processing reliability improvements +- Support h for hardened children in BIP32 derivation paths + +### FIXED + +- Visual bug regarding welcomeToMuun dialog width in foldables and tablets. + +### CHANGED + +- Upgraded Gradle to 8.10 +- Upgraded Android Gradle Plugin (AGP) to 8.5.2 +- Upgraded Timber to 5.0.1 with related api changes +- Upgraded google-api-client-android:2.7.0 with related api changes +- Upgraded google-http-client-gson:1.45.0 +- Updated linter baseline.xml +- Enhanced errors and email error reports with app exit reasons and lowRam and background restricted +metadata. + + ## [52.5] - 2024-12-02 ### ADDED @@ -97,6 +120,8 @@ errors and crashes ### CHANGED - Upgraded compiledSdkVersion and targetSdkVersion to 34 +- Upgraded Gradle to 7.5.1 +- Upgraded Android Gradle Plugin (AGP) to 7.4.2 - Upgraded go version to 1.21.11 - Enhanced password input for change password flow (consistency with rest of the app) - Enhanced error metadata for strange secure storage errors diff --git a/android/apollo/build.gradle b/android/apollo/build.gradle index 93c3ad7c..6bde9da0 100644 --- a/android/apollo/build.gradle +++ b/android/apollo/build.gradle @@ -25,6 +25,10 @@ android { compileSdk 34 + buildFeatures { + buildConfig true + } + defaultConfig { minSdk 19 targetSdk 34 @@ -34,17 +38,17 @@ android { buildTypes { minified { - minifyEnabled true + minifyEnabled false } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() + jvmTarget = JavaVersion.VERSION_17.toString() } lint { @@ -76,6 +80,11 @@ ext { version_mockk = '1.13.7' // Latest version targeting kotlin 1.8.20 } +configurations { + all { + exclude group: 'org.apache.httpcomponents', module: 'httpclient' + } +} dependencies { api project(':common') @@ -100,7 +109,7 @@ dependencies { compileOnly 'org.glassfish:javax.annotation:10.0-b28' // Logging: - api 'com.jakewharton.timber:timber:4.5.1' + api 'com.jakewharton.timber:timber:5.0.1' // Money: api 'org.javamoney:moneta-bp:1.0' @@ -137,9 +146,9 @@ dependencies { // Google APIs implementation 'com.google.android.gms:play-services-auth:20.6.0' - implementation 'com.google.api-client:google-api-client-android:1.26.0' + implementation('com.google.api-client:google-api-client-android:2.7.0') implementation 'com.google.apis:google-api-services-drive:v3-rev136-1.25.0' - implementation 'com.google.http-client:google-http-client-gson:1.26.0' + implementation 'com.google.http-client:google-http-client-gson:1.45.0' // Kotlin: // Kotlin serialization runtime library. Note that while the plugin has version the same as diff --git a/android/apollo/lint-baseline.xml b/android/apollo/lint-baseline.xml index c119177a..d34213c1 100644 --- a/android/apollo/lint-baseline.xml +++ b/android/apollo/lint-baseline.xml @@ -1,43 +1,15 @@ - - - - - - - - - - - - - - - - - + + errorLine1=" .commit()" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/data/preferences/BaseRepository.kt" + line="37" + column="14"/> @@ -69,7 +41,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -80,7 +52,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -91,7 +63,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -102,7 +74,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -113,7 +85,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -124,7 +96,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -135,24 +107,13 @@ errorLine2=" ~~~~~~~~~~~"> - - - - + message="Value must be ≥ 0 but `getColumnIndex` can be -1" + errorLine1=" element.setId(cursor.getLong(cursor.getColumnIndex("id")));" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="44" + column="50"/> @@ -245,7 +206,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -256,7 +217,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -267,19 +228,41 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="A newer version of com.google.firebase:firebase-bom than 32.1.1 is available: 33.6.0" + errorLine1=" api platform('com.google.firebase:firebase-bom:32.1.1')" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="120" + column="9"/> + + + + + + + + + message="A newer version of org.jetbrains.kotlinx:kotlinx-serialization-json than 1.2.1 is available: 1.6.3" + errorLine1=" api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="157" + column="9"/> + message="A newer version of com.google.android.play:integrity than 1.1.0 is available: 1.4.0" + errorLine1=" implementation('com.google.android.play:integrity:1.1.0')" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + @@ -344,90 +349,343 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" ForceFetchFcmAction forceFetchFcmTokenAction();" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/domain/action/di/ActionComponent.java" + line="100" + column="5"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.http-client/google-http-client/1.45.0/63eb7643e01797d6aa67c75ae31ffcc6c365db18/google-http-client-1.45.0.jar"/> + file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.http-client/google-http-client/1.45.0/63eb7643e01797d6aa67c75ae31ffcc6c365db18/google-http-client-1.45.0.jar"/> + id="Recycle" + message="This `InputStream` should be freed up after use with `#close()`" + errorLine1=" inputStream = context.getContentResolver().openInputStream(contentUri);" + errorLine2=" ~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/data/net/okio/ContentUriRequestBody.java" + line="49" + column="56"/> + id="ObsoleteSdkInt" + message="Unnecessary; SDK_INT is always >= 18" + errorLine1=" @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="ObsoleteSdkInt" + message="Unnecessary; SDK_INT is always >= 18" + errorLine1=" @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="ObsoleteSdkInt" + message="Unnecessary; SDK_INT is always >= 18" + errorLine1=" @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + - - - - diff --git a/android/apollo/src/main/AndroidManifest.xml b/android/apollo/src/main/AndroidManifest.xml index ca99cd26..3b757b0a 100644 --- a/android/apollo/src/main/AndroidManifest.xml +++ b/android/apollo/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - diff --git a/android/apollo/src/main/java/io/muun/apollo/data/apis/DriveImpl.kt b/android/apollo/src/main/java/io/muun/apollo/data/apis/DriveImpl.kt index 419003a1..5eaabb47 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/apis/DriveImpl.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/apis/DriveImpl.kt @@ -10,9 +10,9 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.common.Scopes import com.google.android.gms.common.api.Scope import com.google.android.gms.tasks.Tasks -import com.google.api.client.extensions.android.http.AndroidHttp import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential import com.google.api.client.http.FileContent +import com.google.api.client.http.javanet.NetHttpTransport import com.google.api.client.json.gson.GsonFactory import com.google.api.services.drive.Drive import com.google.api.services.drive.DriveScopes @@ -112,9 +112,7 @@ class DriveImpl @Inject constructor( credential.selectedAccount = GoogleSignIn.getLastSignedInAccount(context)!!.account - val transport = AndroidHttp.newCompatibleTransport() - - return Drive.Builder(transport, GsonFactory(), credential) + return Drive.Builder(NetHttpTransport(), GsonFactory(), credential) .setApplicationName("Muun") .build() } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/di/DataComponent.java b/android/apollo/src/main/java/io/muun/apollo/data/di/DataComponent.java index f8e0fd6a..47d1b766 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/di/DataComponent.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/di/DataComponent.java @@ -30,6 +30,7 @@ import io.muun.apollo.domain.action.LogoutActions; import io.muun.apollo.domain.action.di.ActionComponent; import io.muun.apollo.domain.analytics.Analytics; +import io.muun.apollo.domain.libwallet.LibwalletService; import android.content.Context; import dagger.Component; @@ -113,4 +114,6 @@ public interface DataComponent extends ActionComponent { Analytics analytics(); DaoManager daoManager(); + + LibwalletService goLibwalletService(); } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/di/DataModule.java b/android/apollo/src/main/java/io/muun/apollo/data/di/DataModule.java index 12203d6d..05d51ee1 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/di/DataModule.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/di/DataModule.java @@ -20,6 +20,8 @@ import io.muun.apollo.data.preferences.RepositoryRegistry; import io.muun.apollo.domain.action.NotificationActions; import io.muun.apollo.domain.action.NotificationPoller; +import io.muun.apollo.domain.libwallet.GoLibwalletService; +import io.muun.apollo.domain.libwallet.LibwalletService; import android.content.Context; import com.fasterxml.jackson.databind.ObjectMapper; @@ -42,7 +44,12 @@ public class DataModule { private final Context applicationContext; - private final Func3 + private final Func3< + Context, + ExecutionTransformerFactory, + RepositoryRegistry, + NotificationService + > notificationServiceFactory; private final Func1 appStandbyBucketProviderFactory; @@ -54,7 +61,12 @@ public class DataModule { */ public DataModule( Context applicationContext, - Func3 notificationServiceFactory, + Func3< + Context, + ExecutionTransformerFactory, + RepositoryRegistry, + NotificationService + > notificationServiceFactory, Func1 appStandbyBucketProviderFactory, HoustonConfig houstonConfig ) { @@ -189,4 +201,9 @@ RepositoryRegistry provideRepositoryRegistry() { NotificationPoller provideNotificationPoller(final NotificationActions notificationActions) { return notificationActions; } + + @Provides + LibwalletService provideLibwalletService() { + return new GoLibwalletService(); + } } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt b/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt index 97c70426..0b80a4d6 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/logging/Crashlytics.kt @@ -4,6 +4,7 @@ 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.ActivityManagerInfoProvider import io.muun.apollo.data.os.GooglePlayServicesHelper import io.muun.apollo.data.os.OS import io.muun.apollo.data.os.TelephonyInfoProvider @@ -32,6 +33,7 @@ object Crashlytics { } private var analyticsProvider: AnalyticsProvider? = null + private var activityManagerInfoProvider: ActivityManagerInfoProvider? = null private var bigQueryPseudoId: String? = null @@ -55,6 +57,8 @@ object Crashlytics { this.defaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler(customUncaughtExceptionHandler) + + this.activityManagerInfoProvider = ActivityManagerInfoProvider(application) } // enhance crashlytics crashes with custom keys @@ -101,7 +105,6 @@ object Crashlytics { // 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) setStaticCustomKeys() @@ -123,10 +126,26 @@ object Crashlytics { crashlytics?.setCustomKey("abi", getSupportedAbi()) crashlytics?.setCustomKey("isPlayServicesAvailable", googlePlayServicesAvailable.toString()) crashlytics?.setCustomKey( - "installSource-installingPackage", installSource?.installingPackageName ?: "null" + "installSource-installingPackage", + installSource?.installingPackageName ?: "null" + ) + crashlytics?.setCustomKey( + "installSource-initiatingPackage", + installSource?.initiatingPackageName ?: "null" + ) + + crashlytics?.setCustomKey("isLowRamDevice", activityManagerInfoProvider!!.isLowRamDevice) + crashlytics?.setCustomKey( + "isBackgroundRestricted", + activityManagerInfoProvider!!.isBackgroundRestricted + ) + crashlytics?.setCustomKey( + "isRunningInUserTestHarness", + activityManagerInfoProvider!!.isRunningInUserTestHarness ) crashlytics?.setCustomKey( - "installSource-initiatingPackage", installSource?.initiatingPackageName ?: "null" + "isLowMemoryKillReportSupported", + activityManagerInfoProvider!!.isLowMemoryKillReportSupported ) } @@ -143,13 +162,11 @@ object Crashlytics { * message, error) and the error that happened while reporting. */ fun reportReportingError( - tag: String?, message: String?, originalError: Throwable?, crashReportingError: Throwable, ) { - tag?.let { crashlytics?.setCustomKey("tag", it) } message?.let { crashlytics?.setCustomKey("message", it) } if (originalError != null) { diff --git a/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt b/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt index 79f7d20b..6b380e92 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/logging/MuunTree.kt @@ -8,68 +8,68 @@ class MuunTree : Timber.DebugTree() { /** * Log a message, taking steps to enrich and report errors. + * Automatically infers the tag from the calling class (See Timber.DebugTree). */ - override fun log(priority: Int, tag: String?, message: String?, error: Throwable?) { + override fun log(priority: Int, error: Throwable?, message: String?, vararg args: Any?) { // For low priority logs, we don't have any special treatment: if (priority < Log.INFO) { - sendToLogcat(priority, tag, message, error) + sendToLogcat(priority, message, error) return } when (priority) { Log.INFO -> { - sendToLogcat(Log.INFO, "Breadcrumb", message!!, null) + sendToLogcat(Log.INFO, "Breadcrumb: ${message!!}", null) @Suppress("DEPRECATION") // I know. These are the only allowed usages. Crashlytics.logBreadcrumb(message) } Log.WARN -> { - sendToLogcat(Log.WARN, tag, message!!, null) + sendToLogcat(Log.WARN, message!!, null) @Suppress("DEPRECATION") // I know. These are the only allowed usages. Crashlytics.logBreadcrumb("Warning: $message") } else -> { // Log.ERROR && Log.ASSERT - sendCrashReport(tag, message, error) + sendCrashReport(message, error) } } } - private fun sendCrashReport(tag: String?, message: String?, error: Throwable?) { + private fun sendCrashReport(message: String?, error: Throwable?) { try { - sendPreparedCrashReport(tag, message, error) + sendPreparedCrashReport(message, error) } catch (crashReportingError: Throwable) { - sendFallbackCrashReport(tag, message, error, crashReportingError) + sendFallbackCrashReport(message, error, crashReportingError) } } - private fun sendPreparedCrashReport(tag: String?, message: String?, error: Throwable?) { - val report = CrashReportBuilder.build(tag, message, error) + private fun sendPreparedCrashReport(message: String?, error: Throwable?) { + val report = CrashReportBuilder.build(message, error) if (LoggingContext.sendToCrashlytics) { Crashlytics.reportError(report) } - sendToLogcat(Log.ERROR, report.tag, "${report.message} ${report.metadata}", report.error) + sendToLogcat(Log.ERROR, "${report.message} ${report.metadata}", report.error) } private fun sendFallbackCrashReport( - tag: String?, message: String?, error: Throwable?, crashReportingError: Throwable, ) { - sendToLogcat(Log.ERROR, "CrashReport:$tag", "During error processing", crashReportingError) - sendToLogcat(Log.ERROR, "CrashReport:$tag", message, error) + sendToLogcat(Log.ERROR, "During error processing", crashReportingError) + sendToLogcat(Log.ERROR, message, error) if (LoggingContext.sendToCrashlytics) { - Crashlytics.reportReportingError(tag, message, error, crashReportingError) + Crashlytics.reportReportingError(message, error, crashReportingError) } } - private fun sendToLogcat(priority: Int, tag: String?, message: String?, error: Throwable?) { + private fun sendToLogcat(priority: Int, message: String?, error: Throwable?) { if (LoggingContext.sendToLogcat) { - super.log(priority, tag, message, error) + super.log(priority, message, error) } } } \ No newline at end of file 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 a61a8593..544fe9fe 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 @@ -229,7 +229,10 @@ public ClientJson mapClient( final int emArchitecture, final String securityEnhancedBuild, final String bridgeRootService, - final long appSize + final long appSize, + final List hardwareAddresses, + final String vbMeta, + final String efsCreationTimeInSeconds ) { return new ClientJson( ClientTypeJson.APOLLO, @@ -272,7 +275,10 @@ public ClientJson mapClient( emArchitecture, mapSeLinux(securityEnhancedBuild), mapAdbRootService(bridgeRootService), - appSize + appSize, + hardwareAddresses, + vbMeta, + efsCreationTimeInSeconds ); } @@ -398,15 +404,18 @@ public CreateFirstSessionJson mapCreateFirstSession( @NonNull CpuInfo cpuInfo, @NonNull GooglePlayServicesHelper.PlayServicesInfo playServicesInfo, @NonNull GooglePlayHelper.PlayInfo playInfo, - BuildInfoProvider.BuildInfo buildInfo, - PackageManagerInfoProvider.AppInfo appInfo, - PackageManagerInfoProvider.DeviceFeatures deviceFeatures, - String signatureHash, - Integer quickEmProps, - Integer emArchitecture, - String securityEnhancedBuild, - String bridgeRootService, - Long appSize + final BuildInfoProvider.BuildInfo buildInfo, + final PackageManagerInfoProvider.AppInfo appInfo, + final PackageManagerInfoProvider.DeviceFeatures deviceFeatures, + final String signatureHash, + final Integer quickEmProps, + final Integer emArchitecture, + final String securityEnhancedBuild, + final String bridgeRootService, + final Long appSize, + final List hardwareAddresses, + final String vbMeta, + final String efsCreationTimeInSeconds ) { return new CreateFirstSessionJson( @@ -430,7 +439,10 @@ public CreateFirstSessionJson mapCreateFirstSession( emArchitecture, securityEnhancedBuild, bridgeRootService, - appSize + appSize, + hardwareAddresses, + vbMeta, + efsCreationTimeInSeconds ), gcmToken, primaryCurrency, @@ -464,7 +476,10 @@ public CreateLoginSessionJson mapCreateLoginSession( final Integer emArchitecture, final String securityEnhancedBuild, final String bridgeRootService, - final Long appSize + final Long appSize, + final List hardwareAddresses, + final String vbMeta, + final String efsCreationTimeInSeconds ) { return new CreateLoginSessionJson( @@ -488,7 +503,10 @@ public CreateLoginSessionJson mapCreateLoginSession( emArchitecture, securityEnhancedBuild, bridgeRootService, - appSize + appSize, + hardwareAddresses, + vbMeta, + efsCreationTimeInSeconds ), gcmToken, email @@ -520,7 +538,10 @@ public CreateRcLoginSessionJson mapCreateRcLoginSession( final Integer emArchitecture, final String securityEnhancedBuild, final String bridgeRootService, - final Long appSize + final Long appSize, + final List hardwareAddresses, + final String vbMeta, + final String efsCreationTimeInSeconds ) { return new CreateRcLoginSessionJson( @@ -544,7 +565,10 @@ public CreateRcLoginSessionJson mapCreateRcLoginSession( emArchitecture, securityEnhancedBuild, bridgeRootService, - appSize + appSize, + hardwareAddresses, + vbMeta, + efsCreationTimeInSeconds ), gcmToken, new ChallengeKeyJson( diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt index d0080f8c..6923c503 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/ConnectivityInfoProvider.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.Serializable import javax.inject.Inject // TODO we should merge this and NetworkInfoProvider together -class ConnectivityInfoProvider @Inject constructor(private val context: Context) { +class ConnectivityInfoProvider @Inject constructor(context: Context) { private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager @@ -24,7 +24,7 @@ class ConnectivityInfoProvider @Inject constructor(private val context: Context) val routesInterfaces: Set?, val hasGatewayRoute: Int, val dnsAddresses: Set?, - val linkHttpProxyHost: String? + val linkHttpProxyHost: String?, ) val vpnState: Int @@ -45,17 +45,17 @@ class ConnectivityInfoProvider @Inject constructor(private val context: Context) val proxyHttp: String get() { - return System.getProperty("http.proxyHost") ?: "" + return System.getProperty("http.proxyHost") ?: Constants.EMPTY } val proxyHttps: String get() { - return System.getProperty("https.proxyHost") ?: "" + return System.getProperty("https.proxyHost") ?: Constants.EMPTY } val proxySocks: String get() { - return System.getProperty("socks.proxyHost") ?: "" + return System.getProperty("socks.proxyHost") ?: Constants.EMPTY } @@ -68,38 +68,39 @@ class ConnectivityInfoProvider @Inject constructor(private val context: Context) */ val currentTransportNewerApi: String get() { - if (OS.supportsActiveNetwork()) { - val connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val activeNetwork = connectivityManager.activeNetwork ?: return "UNKNOWN" - val networkCapabilities = - connectivityManager.getNetworkCapabilities(activeNetwork) ?: return "UNKNOWN" - return when { - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + if (!OS.supportsActiveNetwork()) { + return Constants.UNKNOWN + } + + val activeNetwork = connectivityManager.activeNetwork ?: return Constants.UNKNOWN + val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork) + ?: return Constants.UNKNOWN + + return when { + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "WIFI" - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "MOBILE" - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> "BLUETOOTH" - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> "ETHERNET" - networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) + networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> "VPN" - else -> "UNKNOWN" - } + else -> Constants.UNKNOWN } - return "UNKNOWN" } val networkLink: NetworkLink? get() { - if (!OS.supportsActiveNetwork()) + if (!OS.supportsActiveNetwork()) { return null + } val activeNetwork = connectivityManager.activeNetwork val linkProperties = connectivityManager.getLinkProperties(activeNetwork) @@ -118,13 +119,14 @@ class ConnectivityInfoProvider @Inject constructor(private val context: Context) hasGatewayRoute, dnsAddresses, linkHttpProxyHost - ); + ) } private fun getHasGatewayRoute(linkProperties: LinkProperties?): Int { - if(!OS.supportsRouteHasGateway()) { - return -1 + if (!OS.supportsRouteHasGateway()) { + return Constants.INT_UNKNOWN } + return linkProperties?.routes ?.any { route -> route.hasGateway() } ?.let { if (it) 1 else 0 } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/net/HoustonClient.java b/android/apollo/src/main/java/io/muun/apollo/data/net/HoustonClient.java index f5a7772e..65de72d3 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/net/HoustonClient.java +++ b/android/apollo/src/main/java/io/muun/apollo/data/net/HoustonClient.java @@ -191,7 +191,10 @@ public Observable createFirstSession( fileInfoProvider.getEmArchitecture(), systemCapabilitiesProvider.getSecurityEnhancedBuild(), systemCapabilitiesProvider.getBridgeRootService(), - fileInfoProvider.getAppSize() + fileInfoProvider.getAppSize(), + hardwareCapabilitiesProvider.getHardwareAddresses(), + systemCapabilitiesProvider.getVbMeta(), + fileInfoProvider.getEfsCreationTimeInSeconds() ); return getService().createFirstSession(params) @@ -230,7 +233,10 @@ public Observable createLoginSession( fileInfoProvider.getEmArchitecture(), systemCapabilitiesProvider.getSecurityEnhancedBuild(), systemCapabilitiesProvider.getBridgeRootService(), - fileInfoProvider.getAppSize() + fileInfoProvider.getAppSize(), + hardwareCapabilitiesProvider.getHardwareAddresses(), + systemCapabilitiesProvider.getVbMeta(), + fileInfoProvider.getEfsCreationTimeInSeconds() ); return getService().createLoginSession(params) @@ -269,7 +275,11 @@ public Observable createRcLoginSession( fileInfoProvider.getEmArchitecture(), systemCapabilitiesProvider.getSecurityEnhancedBuild(), systemCapabilitiesProvider.getBridgeRootService(), - fileInfoProvider.getAppSize() + fileInfoProvider.getAppSize(), + hardwareCapabilitiesProvider.getHardwareAddresses(), + systemCapabilitiesProvider.getVbMeta(), + fileInfoProvider.getEfsCreationTimeInSeconds() + ); return getService().createRecoveryCodeLoginSession(session) diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt index b1342b1c..a031cfbb 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/ActivityManagerInfoProvider.kt @@ -1,10 +1,16 @@ package io.muun.apollo.data.os import android.app.ActivityManager +import android.app.ApplicationExitInfo +import android.content.Context +import io.muun.apollo.data.external.Globals import javax.inject.Inject -class ActivityManagerInfoProvider @Inject constructor() { +class ActivityManagerInfoProvider @Inject constructor(context: Context) { + + private val activityManager: ActivityManager = + context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val appImportance: Int get() { @@ -14,4 +20,73 @@ class ActivityManagerInfoProvider @Inject constructor() { ActivityManager.getMyMemoryState(appProcessInfo) return appProcessInfo.importance } + + /** + * Returns true if this is a low-RAM device. Exactly whether a device is low-RAM is ultimately + * up to the device configuration, but currently it generally means something with 1GB or less + * of RAM. This is mostly intended to be used by apps to determine whether they should turn off + * certain features that require more RAM. + * AKA "is Android GO". + */ + val isLowRamDevice: Boolean + get() { + return activityManager.isLowRamDevice + } + + /** + * Query whether the user has enabled background restrictions for this app. + * The user may chose to do this, if they see that an app is consuming an unreasonable + * amount of battery while in the background + * If true, any work that the app tries to do will be aggressively restricted while it is in the + * background. At a minimum, jobs and alarms will not execute and foreground services cannot be + * started unless an app activity is in the foreground. + * + * Note that these restrictions stay in effect even when the device is charging. + */ + val isBackgroundRestricted: Boolean + get() { + return if (OS.supportsIsBackgroundRestricted()) { + activityManager.isBackgroundRestricted + } else { + false + } + } + + val isRunningInUserTestHarness: Boolean + get() { + return if (OS.supportsIsRunningInUserTestHarness()) { + ActivityManager.isRunningInUserTestHarness() + } else { + @Suppress("DEPRECATION") // Deprecated, prefer isRunningInUserTestHarness + ActivityManager.isRunningInTestHarness() + } + } + + val isUserAMonkey: Boolean + get() { + return ActivityManager.isUserAMonkey() + } + + val isLowMemoryKillReportSupported: Boolean + get() { + return if (OS.supportsLowMemoryKillReport()) { + ActivityManager.isLowMemoryKillReportSupported() + } else { + false + } + } + + val exitReasons: List + get() { + return if (OS.supportsgetHistoricalProcessExitReasons()) { + activityManager.getHistoricalProcessExitReasons( + Globals.INSTANCE.applicationId, + 0, + 0 + ) + } else { + emptyList() + } + } + } \ 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 4631afa6..4b05d6bb 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 @@ -80,9 +80,7 @@ class BackgroundExecutionMetricsProvider @Inject constructor( dateTimeZoneProvider.calendarIdentifier, trafficStatsInfoProvider.androidMobileRxTraffic, telephonyInfoProvider.simOperatorId, - telephonyInfoProvider.simOperatorName, telephonyInfoProvider.mobileNetworkId, - telephonyInfoProvider.mobileNetworkName, telephonyInfoProvider.mobileRoaming, telephonyInfoProvider.mobileDataStatus, telephonyInfoProvider.mobileRadioType, @@ -91,7 +89,10 @@ class BackgroundExecutionMetricsProvider @Inject constructor( nfcProvider.hasNfcFeature(), nfcProvider.hasNfcAdapter, nfcProvider.isNfcEnabled, - nfcProvider.getNfcAntennaPosition().map { "${it.first};${it.second}" }.toTypedArray() + nfcProvider.getNfcAntennaPosition().map { "${it.first};${it.second}" }.toTypedArray(), + nfcProvider.deviceSizeInMm?.let { "${it.first};${it.second}" } ?: "", + nfcProvider.isDeviceFoldable, + activityManagerInfoProvider.isBackgroundRestricted ) @Suppress("ArrayInDataClass") @@ -139,9 +140,7 @@ class BackgroundExecutionMetricsProvider @Inject constructor( 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, @@ -150,7 +149,10 @@ class BackgroundExecutionMetricsProvider @Inject constructor( private val androidHasNfcFeature: Boolean, private val androidHasNfcAdapter: Boolean, private val androidNfcEnabled: Boolean, - private val androidNfcAntennaPositions: Array + private val androidNfcAntennaPositions: Array, // in mms starting bottom-left + private val androidDeviceSizeInMms: String, + private val androidFoldableDevice: Boolean?, + private val isBackgroundRestricted: Boolean ) /** diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt index 78597a5c..952c0c3f 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/Constants.kt @@ -1,10 +1,13 @@ package io.muun.apollo.data.os object Constants { - const val INT_UNKNOWN = -1 const val UNKNOWN = "UNKNOWN" - const val PRESENT = 1 - const val ABSENT = 0 const val EMPTY = "" + const val ERROR = "ERROR" + + // INT constants const val INT_EXCEPTION = -2 + const val INT_UNKNOWN = -1 + const val INT_ABSENT = 0 + const val INT_PRESENT = 1 } \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/FileInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/FileInfoProvider.kt index 1b6544a3..42e1faa0 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/FileInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/FileInfoProvider.kt @@ -5,6 +5,9 @@ import android.os.Environment import timber.log.Timber import java.io.File import java.io.RandomAccessFile +import java.nio.file.Files +import java.nio.file.attribute.BasicFileAttributes +import java.util.concurrent.TimeUnit import javax.inject.Inject class FileInfoProvider @Inject constructor(private val context: Context) { @@ -12,9 +15,9 @@ class FileInfoProvider @Inject constructor(private val context: Context) { val quickEmProps: Int get() { return if (File(TorHelper.process("/flfgrz/ova/drzh-cebcf")).exists()) { - Constants.PRESENT + Constants.INT_PRESENT } else { - Constants.ABSENT + Constants.INT_ABSENT } } @@ -52,6 +55,24 @@ class FileInfoProvider @Inject constructor(private val context: Context) { } } + val efsCreationTimeInSeconds: String + get() { + if (!OS.supportsReadFileAttributes()) { + return Constants.UNKNOWN + } + val file = File("/efs") + if (!file.exists()) { + return Constants.EMPTY + } + return try { + val attr = + Files.readAttributes(file.toPath(), BasicFileAttributes::class.java) + attr.creationTime().to(TimeUnit.SECONDS).toString() + } catch (e: Exception) { + Constants.ERROR + } + } + private fun findExistingFile(fileNames: List): File? { for (fileName in fileNames) { val file = File(Environment.getRootDirectory(), fileName) diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/HardwareCapabilitiesProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/HardwareCapabilitiesProvider.kt index 550ed214..471e5ee2 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/HardwareCapabilitiesProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/HardwareCapabilitiesProvider.kt @@ -18,6 +18,7 @@ import io.muun.common.utils.Encodings import io.muun.common.utils.Hashes import timber.log.Timber import java.io.File +import java.net.NetworkInterface import java.util.* import javax.inject.Inject @@ -194,6 +195,31 @@ class HardwareCapabilitiesProvider @Inject constructor(private val context: Cont } } + + val hardwareAddresses: List + get() { + if (!OS.supportsHardwareAddresses()) { + return emptyList() + } + return try { + NetworkInterface.getNetworkInterfaces()?.asSequence()?.toList() + ?.filter { it.hardwareAddress != null } + ?.filter { + it.displayName.startsWith("wlan") || + it.displayName.startsWith("p2p") + } + ?.map { networkInterface -> + networkInterface.hardwareAddress + .joinToString("-") { byte -> + "%02X".format(byte) + } + } + ?.sortedBy { it }?.toList() ?: emptyList() + } catch (e: Exception) { + emptyList() + } + } + private fun File.getTotalSpaceSafe() = try { totalSpace } catch (e: Exception) { 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 index 66e36a47..7c61852b 100644 --- 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 @@ -27,6 +27,10 @@ class NfcProvider @Inject constructor(private val context: Context) { val isNfcEnabled: Boolean get() = nfcAdapter != null && nfcAdapter.isEnabled + /** + * Location of the antenna in millimeters. 0 is the bottom-left when the user is facing the + * screen and the device orientation is Portrait. + */ fun getNfcAntennaPosition(): List> { val result = mutableListOf>() @@ -43,4 +47,38 @@ class NfcProvider @Inject constructor(private val context: Context) { return result } + + val deviceSizeInMm: Pair? + get() { + try { + return if (OS.supportsAvailableNfcAntennas()) { + // nfcAntennaInfo was added alongside with availableNfcAntennas + nfcAdapter?.nfcAntennaInfo?.let { nfcAntennaInfo -> + Pair(nfcAntennaInfo.deviceWidth, nfcAntennaInfo.deviceHeight) + } + + } else { + null + } + } catch (e: Exception) { + Timber.i("Error while reading NFC data from NFC compat device: ${e.message}") + return null + } + } + + val isDeviceFoldable: Boolean? + get() { + try { + return if (OS.supportsAvailableNfcAntennas()) { + // nfcAntennaInfo was added alongside with availableNfcAntennas + nfcAdapter?.nfcAntennaInfo?.isDeviceFoldable + + } else { + null + } + } catch (e: Exception) { + Timber.i("Error while reading NFC data from NFC compat device: ${e.message}") + return null + } + } } \ 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 c2da5b35..8297a075 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,9 +229,51 @@ object OS { fun supportsKeystoreExceptionPublicMethods(): Boolean = isAndroidTiramisuOrNewer() + /** + * Whether this OS supports {@link android.nfc.NfcAntennaInfo.getAvailableNfcAntennas}, which + * was introduced in U-14-34. + */ fun supportsAvailableNfcAntennas(): Boolean = isAndroidUpsideDownCakeOrNewer() + /** + * Whether this OS supports {@link android.app.ActivityManager.isBackgroundRestricted}, which + * was introduced in P-9-28. + */ + fun supportsIsBackgroundRestricted(): Boolean = + isAndroidPOrNewer() + + /** + * Whether this OS supports {@link android.app.ActivityManager.isRunningInUserTestHarness}, + * which was introduced in Q-10-29. + */ + fun supportsIsRunningInUserTestHarness(): Boolean = + isAndroidQOrNewer() + + /** + * Whether this OS supports {@link android.app.ActivityManager.isLowMemoryKillReportSupported}, + * which was introduced in R-11-30. + */ + fun supportsLowMemoryKillReport(): Boolean = + isAndroidROrNewer() + + /** + * Whether this OS supports {@link android.app.ActivityManager.getHistoricalProcessExitReasons}, + * which was introduced in R-11-30. + */ + fun supportsgetHistoricalProcessExitReasons(): Boolean = + isAndroidROrNewer() + + /** + * Whether this OS supports File#readAttributes, which was added in O-8-26. + */ + fun supportsReadFileAttributes(): Boolean = + isAndroidOOrNewer() + + + fun supportsHardwareAddresses(): Boolean = + isAndroidQOrOlder() + // PRIVATE STUFF: @@ -256,6 +298,12 @@ object OS { private fun isAndroidSOrNewer() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + /** + * Whether this OS version is Q-10-29 or older. + */ + private fun isAndroidQOrOlder() = + Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q + /** * Whether this OS version is EXACTLY Q-10-29. */ diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/PackageManagerInfoProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/PackageManagerInfoProvider.kt index 02cd4947..79c024b4 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/PackageManagerInfoProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/PackageManagerInfoProvider.kt @@ -121,9 +121,9 @@ class PackageManagerInfoProvider @Inject constructor(private val context: Contex private fun hasFeature(packageManager: PackageManager, feature: String): Int { return if (packageManager.hasSystemFeature(feature)) { - Constants.PRESENT + Constants.INT_PRESENT } else { - Constants.ABSENT + Constants.INT_ABSENT } } diff --git a/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt b/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt index b243d255..0b2aa7d9 100644 --- a/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt +++ b/android/apollo/src/main/java/io/muun/apollo/data/os/SystemCapabilitiesProvider.kt @@ -58,8 +58,13 @@ class SystemCapabilitiesProvider @Inject constructor(private val context: Contex return getSysProp(TorHelper.process("freivpr.nqo.ebbg")) } + val vbMeta: String + get() { + return getSysProp(TorHelper.process("eb.obbg.iozrgn.qvtrfg")) + } + @SuppressLint("PrivateApi") - fun getSysProp(name: String): String { + private fun getSysProp(name: String): String { return try { val systemPropertyClass: Class<*> = Class.forName(TorHelper.process("naqebvq.bf.FlfgrzCebcregvrf")) 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 5ae006ab..cc728499 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 @@ -85,21 +85,11 @@ open class TelephonyInfoProvider @Inject constructor(context: Context) { return telephonyManager.simOperator } - val simOperatorName: String - get() { - return telephonyManager.simOperatorName - } - val mobileNetworkId: String get() { return telephonyManager.networkOperator } - val mobileNetworkName: String - get() { - return telephonyManager.networkOperatorName - } - val mobileRoaming: Boolean get() { return telephonyManager.isNetworkRoaming 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 fe82d79e..3bf66032 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 @@ -1,6 +1,7 @@ package io.muun.apollo.domain import android.content.Context +import io.muun.apollo.data.os.ActivityManagerInfoProvider import io.muun.apollo.data.os.GooglePlayHelper import io.muun.apollo.data.os.GooglePlayServicesHelper import io.muun.apollo.data.os.TelephonyInfoProvider @@ -21,6 +22,7 @@ class EmailReportManager @Inject constructor( private val googlePlayServicesHelper: GooglePlayServicesHelper, private val googlePlayHelper: GooglePlayHelper, private val telephonyInfoProvider: TelephonyInfoProvider, + private val activityManagerInfoProvider: ActivityManagerInfoProvider, private val isRootedDeviceAction: IsRootedDeviceAction, private val firebaseInstallationIdRepo: FirebaseInstallationIdRepository, private val context: Context, @@ -55,6 +57,10 @@ class EmailReportManager @Inject constructor( .defaultRegion(telephonyInfoProvider.region.orElse("null")) .rootHint(isRootedDeviceAction.actionNow()) .locale(context.locale()) + .isLowRamDevice(activityManagerInfoProvider.isLowRamDevice) + .isBackgroundRestricted(activityManagerInfoProvider.isBackgroundRestricted) + .isLowMemoryKillReportSupported(activityManagerInfoProvider.isLowMemoryKillReportSupported) + .exitReasons(activityManagerInfoProvider.exitReasons) .build(abridged) } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/NotificationActions.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/NotificationActions.kt index 2ce79e2c..efccca23 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/NotificationActions.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/NotificationActions.kt @@ -26,6 +26,11 @@ import javax.inject.Inject import javax.inject.Named import javax.inject.Singleton +enum class NotificationProcessingState { + STARTED, + COMPLETED +} + @Singleton // important class NotificationActions @Inject constructor( private val notificationRepository: NotificationRepository, @@ -50,6 +55,9 @@ class NotificationActions @Inject constructor( private val reportQueue: PublishSubject = PublishSubject.create() private var reportQueueSub: Subscription? = null + // Notify about the notification processing state. + private val processingSubject: PublishSubject = PublishSubject.create() + @JvmField val pullNotificationsAction: AsyncAction0 @@ -69,6 +77,13 @@ class NotificationActions @Inject constructor( reportQueue.onNext(report) } + /** + * Stream to track the START and COMPLETION of processing a notification batch. + */ + fun getNotificationProcessingState(): Observable { + return processingSubject + } + /** * Pull the latest notifications from Houston. */ @@ -121,6 +136,8 @@ class NotificationActions @Inject constructor( Timber.i("[Notifications] Processing List: " + notifications.mapIds().asString()) + processingSubject.onNext(NotificationProcessingState.STARTED) + Observable.from(notifications) .compose(forEach { notification: NotificationJson -> processNotification(notification) @@ -139,6 +156,9 @@ class NotificationActions @Inject constructor( return@flatMap Observable.just(null) } } + .doOnCompleted { + processingSubject.onNext(NotificationProcessingState.COMPLETED) + } .toCompletable() } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/base/CombineLatestAsyncAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/base/CombineLatestAsyncAction.kt index 52df2189..1e8c4353 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/base/CombineLatestAsyncAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/base/CombineLatestAsyncAction.kt @@ -3,39 +3,20 @@ package io.muun.apollo.domain.action.base import rx.Observable class CombineLatestAsyncAction( - private val actionA: BaseAsyncAction?, - private val actionB: BaseAsyncAction?, + private val actionStateObservableA: Observable>, + private val actionStateObservableB: Observable>, ) { /** * Get the observable state of the action. */ fun getState(): Observable>> = - when { - - actionA != null && actionB != null -> Observable.zip( - actionA.state, - actionB.state, + Observable.zip( + actionStateObservableA, + actionStateObservableB, this::merge ) - actionA != null -> actionA.state.map { - if (it.kind == ActionState.Kind.VALUE) { - ActionState.createValue(Pair(it.value, null)) - } else { - it as ActionState> - } - } - - else -> actionB!!.state.map { - if (it.kind == ActionState.Kind.VALUE) { - ActionState.createValue(Pair(null, it.value)) - } else { - it as ActionState> - } - } - } - /** * Merge the ActionStates from 2 AsyncActions. Rules are: * - While any of them is LOADING -> returned ActionState is LOADING. diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/di/ActionComponent.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/di/ActionComponent.java index f794a324..c4267ac4 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/di/ActionComponent.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/di/ActionComponent.java @@ -29,7 +29,7 @@ import io.muun.apollo.domain.action.operation.SubmitPaymentAction; import io.muun.apollo.domain.action.permission.UpdateContactsPermissionStateAction; import io.muun.apollo.domain.action.realtime.FetchRealTimeDataAction; -import io.muun.apollo.domain.action.realtime.FetchRealTimeFeesAction; +import io.muun.apollo.domain.action.realtime.PreloadFeeDataAction; import io.muun.apollo.domain.action.session.CreateLoginSessionAction; import io.muun.apollo.domain.action.session.LogInAction; import io.muun.apollo.domain.action.session.SyncApplicationDataAction; @@ -83,7 +83,7 @@ public interface ActionComponent { FetchRealTimeDataAction fetchRealTimeDataAction(); - FetchRealTimeFeesAction fetchRealTimeFeesAction(); + PreloadFeeDataAction fetchRealTimeFeesAction(); ResolveOperationUriAction resolveOperationUriAction(); diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/CreateOperationAction.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/CreateOperationAction.java index d339aebf..84e4435b 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/CreateOperationAction.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/CreateOperationAction.java @@ -15,22 +15,30 @@ import io.muun.apollo.domain.model.Operation; import io.muun.common.model.OperationDirection; import io.muun.common.rx.ObservableFn; +import io.muun.common.utils.Preconditions; import rx.Observable; import timber.log.Timber; import javax.inject.Inject; import javax.inject.Singleton; +import javax.validation.constraints.NotNull; @Singleton public class CreateOperationAction { private final TransactionSizeRepository transactionSizeRepository; + private final OperationDao operationDao; + private final PublicProfileDao publicProfileDao; + private final SubmarineSwapDao submarineSwapDao; + private final IncomingSwapDao incomingSwapDao; + private final IncomingSwapHtlcDao incomingSwapHtlcDao; + private final VerifyFulfillableAction verifyFulfillable; private final NotificationService notificationService; @@ -39,14 +47,16 @@ public class CreateOperationAction { * Create a new Operation in the local database, and update the transaction size vector. */ @Inject - public CreateOperationAction(final TransactionSizeRepository transactionSizeRepository, - final OperationDao operationDao, - final PublicProfileDao publicProfileDao, - final SubmarineSwapDao submarineSwapDao, - final NotificationService notificationService, - final IncomingSwapDao incomingSwapDao, - final IncomingSwapHtlcDao incomingSwapHtlcDao, - final VerifyFulfillableAction verifyFulfillable) { + public CreateOperationAction( + final TransactionSizeRepository transactionSizeRepository, + final OperationDao operationDao, + final PublicProfileDao publicProfileDao, + final SubmarineSwapDao submarineSwapDao, + final NotificationService notificationService, + final IncomingSwapDao incomingSwapDao, + final IncomingSwapHtlcDao incomingSwapHtlcDao, + final VerifyFulfillableAction verifyFulfillable + ) { this.transactionSizeRepository = transactionSizeRepository; this.operationDao = operationDao; @@ -61,11 +71,14 @@ public CreateOperationAction(final TransactionSizeRepository transactionSizeRepo /** * Create/Store a new Operation in the local database, and update the transaction size vector. */ - public Observable action(Operation operation, - NextTransactionSize nextTransactionSize) { + public Observable action( + Operation operation, + @NotNull NextTransactionSize nextTransactionSize + ) { return saveOperation(operation) .map(savedOperation -> { Timber.i("Updating next transaction size estimation"); + logStaleNtsUpdate(operation, nextTransactionSize); transactionSizeRepository.setTransactionSize(nextTransactionSize); if (savedOperation.direction == OperationDirection.INCOMING) { @@ -174,4 +187,25 @@ public Observable saveOperation(Operation operation) { } )); } + + private void logStaleNtsUpdate(Operation operation, NextTransactionSize newNts) { + final NextTransactionSize prevNts = transactionSizeRepository.getNextTransactionSize(); + + if (prevNts == null || prevNts.validAtOperationHid == null) { + return; // Not a stale update. We've nothing stored yet or we have smth really old. + } + + final Long newNtsHid = newNts.validAtOperationHid; + Preconditions.checkNotNull(newNtsHid); // New nts' HAVE non-null validAtOperationHid + + // We're interested in the == case too. We want to detect and check if it causes trouble + if (prevNts.validAtOperationHid >= newNtsHid) { + Timber.e( + "Stale NTS update at CreateOperation op.Hid:%s. %s vs %s", + operation.getHid(), + newNtsHid, + prevNts.validAtOperationHid + ); + } + } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/UpdateOperationAction.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/UpdateOperationAction.java index 4ff26de3..269908ff 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/UpdateOperationAction.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/operation/UpdateOperationAction.java @@ -5,11 +5,14 @@ import io.muun.apollo.data.external.NotificationService; import io.muun.apollo.data.preferences.TransactionSizeRepository; import io.muun.apollo.domain.action.base.BaseAsyncAction1; +import io.muun.apollo.domain.model.NextTransactionSize; import io.muun.apollo.domain.model.Operation; import io.muun.apollo.domain.model.OperationUpdated; import io.muun.common.model.OperationStatus; +import io.muun.common.utils.Preconditions; import rx.Observable; +import timber.log.Timber; import javax.inject.Inject; import javax.inject.Singleton; @@ -18,7 +21,9 @@ public class UpdateOperationAction extends BaseAsyncAction1 { private final TransactionSizeRepository transactionSizeRepository; + private final OperationDao operationDao; + private final SubmarineSwapDao submarineSwapDao; private final NotificationService notificationService; @@ -27,10 +32,12 @@ public class UpdateOperationAction extends BaseAsyncAction1 action(OperationUpdated operationUpdated) { .toBlocking() .first(); + Preconditions.checkNotNull(previousOp); // FetchByHid returns non null (or throws). + Preconditions.checkNotNull(previousOp.getId()); // DB stored op HAS an id + if (hasTransitionedToFailure(previousOp, operationUpdated)) { notificationService.showOperationFailedNotification(previousOp.getId()); } @@ -56,7 +66,19 @@ public Observable action(OperationUpdated operationUpdated) { operationUpdated.status ); + final NextTransactionSize prevNts = transactionSizeRepository.getNextTransactionSize(); transactionSizeRepository.setTransactionSize(operationUpdated.nextTransactionSize); + //noinspection StatementWithEmptyBody + if (shouldUpdateNts(operationUpdated, prevNts)) { + // TODO setTransactionSize should be here + } else { + Timber.e( + "Stale NTS update at UpdateOperation op.Hid:%s. %s vs %s", + operationUpdated.hid, + operationUpdated.nextTransactionSize.validAtOperationHid, + prevNts.validAtOperationHid + ); + } if (operationUpdated.submarineSwap != null) { submarineSwapDao.updatePaymentInfo(operationUpdated.submarineSwap); @@ -84,4 +106,17 @@ private boolean shouldConsideredFailed(OperationStatus status) { return false; } } + + /** + * We should update NTS if operation update is newer than the latest known operation we have + * stored. Else, we're receiving a stale update. + */ + private boolean shouldUpdateNts(OperationUpdated operation, NextTransactionSize currentNts) { + + if (currentNts == null || currentNts.validAtOperationHid == null) { + return true; // we have no NTS or a really old one. + } + + return operation.hid > currentNts.validAtOperationHid; + } } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeDataAction.java b/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeDataAction.java index aa0cec03..e6fd905a 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeDataAction.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeDataAction.java @@ -8,6 +8,7 @@ import io.muun.apollo.data.preferences.ForwardingPoliciesRepository; import io.muun.apollo.data.preferences.MinFeeRateRepository; import io.muun.apollo.domain.action.base.BaseAsyncAction0; +import io.muun.apollo.domain.model.MuunFeature; import io.muun.common.rx.RxHelper; import rx.Observable; @@ -74,12 +75,16 @@ private Observable forceSyncRealTimeData() { return houstonClient.fetchRealTimeData() .doOnNext(realTimeData -> { Timber.d("[Sync] Saving updated fee/rates"); - feeWindowRepository.store(realTimeData.feeWindow); exchangeRateWindowRepository.storeLatest(realTimeData.exchangeRateWindow); blockchainHeightRepository.store(realTimeData.currentBlockchainHeight); forwardingPoliciesRepository.store(realTimeData.forwardingPolicies); - minFeeRateRepository.store(realTimeData.minFeeRateInWeightUnits); featuresRepository.store(realTimeData.features); + + // When the FF is ON, this data will be stored by FetchRealTimeFeesAction + if (!realTimeData.features.contains(MuunFeature.EFFECTIVE_FEES_CALCULATION)) { + feeWindowRepository.store(realTimeData.feeWindow); + minFeeRateRepository.store(realTimeData.minFeeRateInWeightUnits); + } }) .map(RxHelper::toVoid); } diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeFeesAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeFeesAction.kt deleted file mode 100644 index f3a109ad..00000000 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/FetchRealTimeFeesAction.kt +++ /dev/null @@ -1,70 +0,0 @@ -package io.muun.apollo.domain.action.realtime - -import io.muun.apollo.data.net.HoustonClient -import io.muun.apollo.data.preferences.FeeWindowRepository -import io.muun.apollo.data.preferences.MinFeeRateRepository -import io.muun.apollo.data.preferences.TransactionSizeRepository -import io.muun.apollo.domain.action.base.BaseAsyncAction0 -import io.muun.apollo.domain.libwallet.errors.FeeBumpFunctionsStoreError -import io.muun.apollo.domain.model.RealTimeFees -import io.muun.apollo.domain.utils.toLibwalletModel -import io.muun.common.Rules -import io.muun.common.rx.RxHelper -import newop.Newop -import rx.Observable -import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Update fees data, such as fee window and fee bump functions. - */ - -@Singleton -class FetchRealTimeFeesAction @Inject constructor( - private val houstonClient: HoustonClient, - private val feeWindowRepository: FeeWindowRepository, - private val minFeeRateRepository: MinFeeRateRepository, - private val transactionSizeRepository: TransactionSizeRepository -) : BaseAsyncAction0() { - override fun action(): Observable { - return Observable.defer { - syncRealTimeFees() - } - } - - private fun syncRealTimeFees(): Observable { - Timber.d("[Sync] Updating realTime fees data") - - transactionSizeRepository.nextTransactionSize?.sizeProgression?.let { - return houstonClient.fetchRealTimeFees(it) - .doOnNext { realTimeFees: RealTimeFees -> - Timber.d("[Sync] Saving updated fees") - storeFeeData(realTimeFees) - storeFeeBumpFunctions(realTimeFees.feeBumpFunctions) - } - .map(RxHelper::toVoid) - } - - Timber.e("syncRealTimeFees was called without a local valid NTS") - return Observable.just(null) - } - - private fun storeFeeData(realTimeFees: RealTimeFees) { - feeWindowRepository.store(realTimeFees.feeWindow) - val minMempoolFeeRateInSatsPerWeightUnit = - Rules.toSatsPerWeight(realTimeFees.minMempoolFeeRateInSatPerVbyte) - minFeeRateRepository.store(minMempoolFeeRateInSatsPerWeightUnit) - } - - private fun storeFeeBumpFunctions(feeBumpFunctions: List) { - val feeBumpFunctionsStringList = feeBumpFunctions.toLibwalletModel() - - try { - Newop.persistFeeBumpFunctions(feeBumpFunctionsStringList) - } catch (e: Exception) { - Timber.e(e, "Error storing fee bump functions") - throw FeeBumpFunctionsStoreError(feeBumpFunctions.toString(), e) - } - } -} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/PreloadFeeDataAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/PreloadFeeDataAction.kt new file mode 100644 index 00000000..dbb8b262 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/realtime/PreloadFeeDataAction.kt @@ -0,0 +1,108 @@ +package io.muun.apollo.domain.action.realtime + +import io.muun.apollo.data.net.HoustonClient +import io.muun.apollo.data.preferences.FeeWindowRepository +import io.muun.apollo.data.preferences.MinFeeRateRepository +import io.muun.apollo.data.preferences.TransactionSizeRepository +import io.muun.apollo.domain.action.base.BaseAsyncAction0 +import io.muun.apollo.domain.libwallet.LibwalletService +import io.muun.apollo.domain.model.MuunFeature +import io.muun.apollo.domain.model.RealTimeFees +import io.muun.apollo.domain.selector.FeatureSelector +import io.muun.apollo.domain.utils.toVoid +import io.muun.common.Rules +import io.muun.common.model.UtxoStatus +import io.muun.common.rx.RxHelper +import rx.Observable +import timber.log.Timber +import java.util.Date +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Update fees data, such as fee window and fee bump functions. + */ + +@Singleton +class PreloadFeeDataAction @Inject constructor( + private val houstonClient: HoustonClient, + private val feeWindowRepository: FeeWindowRepository, + private val minFeeRateRepository: MinFeeRateRepository, + private val transactionSizeRepository: TransactionSizeRepository, + private val featureSelector: FeatureSelector, + private val libwalletService: LibwalletService, +) : BaseAsyncAction0() { + + companion object { + private const val throttleIntervalInMilliseconds: Long = 10 * 1000 + } + + private var lastSyncTime: Date = Date(0) // Init with distant past + + /** + * Force re-fetch of Houston's RealTimeFees, bypassing throttling logic. + */ + fun runForced(): Observable { + return syncRealTimeFees() + } + + fun runIfDataIsInvalidated() { + super.run(Observable.defer { + if (libwalletService.areFeeBumpFunctionsInvalidated()) { + return@defer this.syncRealTimeFees() + } else { + return@defer Observable.just(null) + } + }) + } + + override fun action(): Observable { + return Observable.defer { + if (shouldUpdateData()) { + return@defer syncRealTimeFees() + } else { + return@defer Observable.just(null).toVoid() + } + } + } + + private fun syncRealTimeFees(): Observable { + if (!featureSelector.get(MuunFeature.EFFECTIVE_FEES_CALCULATION)) { + return Observable.just(null) + } + + Timber.d("[Sync] Updating realTime fees data") + + transactionSizeRepository.nextTransactionSize?.let { nts -> + if (nts.unconfirmedUtxos.isEmpty()) { + // If there are no unconfirmed UTXOs, it means there are no fee bump functions. + // Remove the fee bump functions by storing an empty list. + libwalletService.persistFeeBumpFunctions(emptyList()) + } + return houstonClient.fetchRealTimeFees(nts.unconfirmedUtxos) + .doOnNext { realTimeFees: RealTimeFees -> + Timber.d("[Sync] Saving updated fees") + storeFeeData(realTimeFees) + libwalletService.persistFeeBumpFunctions(realTimeFees.feeBumpFunctions) + lastSyncTime = Date() + } + .map(RxHelper::toVoid) + } + + Timber.e("syncRealTimeFees was called without a local valid NTS") + return Observable.just(null) + } + + private fun storeFeeData(realTimeFees: RealTimeFees) { + feeWindowRepository.store(realTimeFees.feeWindow) + val minMempoolFeeRateInSatsPerWeightUnit = + Rules.toSatsPerWeight(realTimeFees.minMempoolFeeRateInSatPerVbyte) + minFeeRateRepository.store(minMempoolFeeRateInSatsPerWeightUnit) + } + + private fun shouldUpdateData(): Boolean { + val nowInMilliseconds = Date().time + val secondsElapsedInMilliseconds = nowInMilliseconds - lastSyncTime.time + return secondsElapsedInMilliseconds >= throttleIntervalInMilliseconds + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/action/session/UseMuunLinkAction.kt b/android/apollo/src/main/java/io/muun/apollo/domain/action/session/UseMuunLinkAction.kt index fb582be8..70f2ae76 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/action/session/UseMuunLinkAction.kt +++ b/android/apollo/src/main/java/io/muun/apollo/domain/action/session/UseMuunLinkAction.kt @@ -16,8 +16,8 @@ import javax.inject.Singleton @VisibleForTesting // open (non-final) class so mockito can mock/spy open class UseMuunLinkAction @Inject constructor( private val houstonClient: HoustonClient, - private val emailLinkAction: EmailLinkAction -): BaseAsyncAction1() { + private val emailLinkAction: EmailLinkAction, +) : BaseAsyncAction1() { private val LINK_PARAM_UUID = "uuid" 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 df6c1c4b..183efe87 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 @@ -514,7 +514,6 @@ sealed class AnalyticsEvent(metadataKeyValues: List> = listOf( class E_CRASHLYTICS_ERROR(report: CrashReport) : AnalyticsEvent( listOf( "title" to report.getTrackingTitle(), - "tag" to report.tag, "message" to report.message, // Trim error stacktraces to avoid problems (not ideal but should be more than enough) "error" to report.printErrorForAnalytics(), diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/GoLibwalletService.kt b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/GoLibwalletService.kt new file mode 100644 index 00000000..215b1a07 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/GoLibwalletService.kt @@ -0,0 +1,23 @@ +package io.muun.apollo.domain.libwallet + +import io.muun.apollo.domain.libwallet.errors.FeeBumpFunctionsStoreError +import io.muun.apollo.domain.utils.toLibwalletModel +import newop.Newop +import timber.log.Timber + +class GoLibwalletService: LibwalletService { + override fun persistFeeBumpFunctions(encodedFeeBumpFunctions: List) { + val feeBumpFunctionsStringList = encodedFeeBumpFunctions.toLibwalletModel() + + try { + Newop.persistFeeBumpFunctions(feeBumpFunctionsStringList) + } catch (e: Exception) { + Timber.e(e, "Error storing fee bump functions") + throw FeeBumpFunctionsStoreError(encodedFeeBumpFunctions.toString(), e) + } + } + + override fun areFeeBumpFunctionsInvalidated(): Boolean { + return Newop.areFeeBumpFunctionsInvalidated() + } +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletService.kt b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletService.kt new file mode 100644 index 00000000..877b007b --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/libwallet/LibwalletService.kt @@ -0,0 +1,8 @@ +package io.muun.apollo.domain.libwallet + +// LibwalletService is a protocol that abstract interactions with Libwallet library. +interface LibwalletService { + // Fee Bump Functions section + fun persistFeeBumpFunctions(encodedFeeBumpFunctions: List) + fun areFeeBumpFunctionsInvalidated(): Boolean +} \ No newline at end of file diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java b/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java index 2ad67a31..192a7fb1 100644 --- a/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java +++ b/android/apollo/src/main/java/io/muun/apollo/domain/model/NextTransactionSize.java @@ -5,6 +5,7 @@ import io.muun.apollo.domain.errors.NullExpectedDebtBugError; import io.muun.common.Supports; import io.muun.common.model.SizeForAmount; +import io.muun.common.model.UtxoStatus; import io.muun.common.utils.Preconditions; import io.muun.common.utils.Since; @@ -162,6 +163,21 @@ public List extractOutpoints() { return outpoints.isEmpty() ? null : outpoints; } + + /** + * Filter and returns a complete list of unconfirmed utxos in current size progression. + */ + public List getUnconfirmedUtxos() { + final List unconfirmedUtxos = new ArrayList<>(); + for (SizeForAmount sizeForAmount : sizeProgression) { + if (sizeForAmount.status == UtxoStatus.UNCONFIRMED) { + unconfirmedUtxos.add(sizeForAmount); + } + } + + return unconfirmedUtxos; + } + /** * Adapt apollo's (java) model to libwallet's (go). */ 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 b2314f82..9c2d88e4 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 @@ -13,7 +13,6 @@ 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, val message: String, val error: Throwable, val originalError: Throwable?, 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 11ef5b4a..061b140c 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 @@ -42,12 +42,12 @@ object CrashReportBuilder { * Build a CrashReport by extracting relevant metadata and summarizing messages and traces. */ fun build(origError: Throwable?): CrashReport = - build(null, origError?.message, origError) + build(origError?.message, origError) /** * Build a CrashReport by extracting relevant metadata and summarizing messages and traces. */ - fun build(tag: String?, origMessage: String?, origError: Throwable?): CrashReport { + fun build(origMessage: String?, origError: Throwable?): CrashReport { // Prepare the message: var message = origMessage ?: "" @@ -71,7 +71,7 @@ object CrashReportBuilder { error = summarize(error) // Done! - return CrashReport(tag ?: "Apollo", message, error, origError, metadata) + return CrashReport(message, error, origError, metadata) } /** Craft a summarized Throwable */ 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 b1df67f2..947fe9ce 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 @@ -1,5 +1,6 @@ package io.muun.apollo.domain.model.report +import android.app.ApplicationExitInfo import android.os.Build import io.muun.apollo.data.external.Globals import io.muun.apollo.domain.utils.getUnsupportedCurrencies @@ -11,7 +12,6 @@ import org.threeten.bp.ZoneOffset import org.threeten.bp.ZonedDateTime import timber.log.Timber import java.util.Locale - import javax.annotation.CheckReturnValue class EmailReport private constructor(val body: String) { @@ -31,6 +31,10 @@ class EmailReport private constructor(val body: String) { var defaultRegion: String? = null, var rootHint: Boolean? = null, var locale: Locale? = null, + var isLowRamDevice: Boolean? = null, + var isBackgroundRestricted: Boolean? = null, + var isLowMemoryKillReportSupported: Boolean? = null, + var exitReasons: List? = null, ) { @CheckReturnValue @@ -87,6 +91,24 @@ class EmailReport private constructor(val body: String) { @CheckReturnValue fun locale(locale: Locale) = apply { this.locale = locale } + @CheckReturnValue + fun isLowRamDevice(isLowRamDevice: Boolean) = apply { this.isLowRamDevice = isLowRamDevice } + + @CheckReturnValue + fun isBackgroundRestricted(isBackgroundRestricted: Boolean) = apply { + this.isBackgroundRestricted = isBackgroundRestricted + } + + @CheckReturnValue + fun isLowMemoryKillReportSupported(isLowMemoryKillReportSupported: Boolean) = apply { + this.isLowMemoryKillReportSupported = isLowMemoryKillReportSupported + } + + @CheckReturnValue + fun exitReasons(exitReasons: List) = apply { + this.exitReasons = exitReasons + } + @CheckReturnValue fun build(abridged: Boolean): EmailReport { @@ -102,6 +124,10 @@ class EmailReport private constructor(val body: String) { checkNotNull(rootHint) checkNotNull(defaultRegion) checkNotNull(locale) + checkNotNull(isLowRamDevice) + checkNotNull(isBackgroundRestricted) + checkNotNull(isLowMemoryKillReportSupported) + checkNotNull(exitReasons) val instant = Instant.ofEpochMilli(System.currentTimeMillis()) val now = ZonedDateTime.ofInstant(instant, ZoneOffset.UTC) @@ -127,11 +153,27 @@ class EmailReport private constructor(val body: String) { |Rooted (just a hint, no guarantees): $rootHint |Unsupported Currencies: ${getUnsupportedCurrencies(report!!).contentToString()} |Default Region: $defaultRegion + |Low Ram Device: $isLowRamDevice + |Background Restricted: $isBackgroundRestricted + |Low Memory Kill Report Supported: $isLowMemoryKillReportSupported + |Recent Exit reasons: ${formatExitReasons(exitReasons!!)} |${report!!.print(abridged)}""".trimMargin() Timber.d("EmailReport: \n$body") return EmailReport(body) } + + private fun formatExitReasons(exitReasons: List): String { + val builder = StringBuilder() + builder.append(" {\n") + + for (exitReason in exitReasons) { + builder.append("\t$exitReason\n") + } + + builder.append("}\n") + return builder.toString() + } } private fun reportId() = diff --git a/android/apollo/src/main/java/io/muun/apollo/domain/sync/FeeDataSyncer.kt b/android/apollo/src/main/java/io/muun/apollo/domain/sync/FeeDataSyncer.kt new file mode 100644 index 00000000..88f3e3e1 --- /dev/null +++ b/android/apollo/src/main/java/io/muun/apollo/domain/sync/FeeDataSyncer.kt @@ -0,0 +1,111 @@ +package io.muun.apollo.domain.sync + +import android.os.Handler +import android.os.Looper +import io.muun.apollo.data.preferences.TransactionSizeRepository +import io.muun.apollo.domain.action.NotificationActions +import io.muun.apollo.domain.action.NotificationProcessingState +import io.muun.apollo.domain.action.realtime.PreloadFeeDataAction +import io.muun.apollo.domain.model.MuunFeature +import io.muun.apollo.domain.model.NextTransactionSize +import io.muun.apollo.domain.selector.FeatureSelector +import io.muun.common.model.SizeForAmount +import io.muun.common.model.UtxoStatus +import rx.subscriptions.CompositeSubscription +import javax.inject.Inject + +/** + * FeeDataSyncer handles NTS changes, observing NotificationActions + * and deciding if it is necessary to refresh fee bump functions. + * It is designed to perform only one refresh per batch of notifications + * processed by NotificationProcessor. Please note that this should be called + * in a serialized manner, meaning that the entire flow of 1 STARTED event + * followed by 1 COMPLETED event must occur sequentially. + * Besides, FeeDataSyncer starts a periodic task that runs every + * `intervalInMilliseconds` milliseconds to update fee bump functions + * in the background. + */ +class FeeDataSyncer @Inject constructor( + private val preloadFeeData: PreloadFeeDataAction, + private val nextTransactionSizeRepository: TransactionSizeRepository, + private val notificationActions: NotificationActions, + private val featureSelector: FeatureSelector, +) { + + private var initialNts: NextTransactionSize? = null + + private val intervalInMilliseconds: Long = 45000 // 45 seconds + private val compositeSubscription = CompositeSubscription() + + private val handler = Handler(Looper.getMainLooper()) + private val periodicTask = object : Runnable { + override fun run() { + preloadFeeData.run() + handler.postDelayed(this, intervalInMilliseconds) + } + } + + fun enterForeground() { + if (!featureSelector.get(MuunFeature.EFFECTIVE_FEES_CALCULATION)) { + return + } + + startPeriodicTask() + beginObservingNtsChanges() + } + + fun enterBackground() { + stopPeriodicTask() + stopObservingNtsChanges() + } + + /** + * Starts a task that runs every `intervalInMilliseconds` milliseconds while + * the app is in foreground to keep the fee data up-to-date. + */ + private fun startPeriodicTask() { + handler.removeCallbacksAndMessages(periodicTask) // to avoid duplicated tasks + handler.postDelayed(periodicTask, intervalInMilliseconds) + } + + private fun stopPeriodicTask() { + handler.removeCallbacksAndMessages(periodicTask) + } + + private fun beginObservingNtsChanges() { + notificationActions.getNotificationProcessingState() + .doOnNext { state -> + when (state) { + NotificationProcessingState.STARTED -> { + initialNts = nextTransactionSizeRepository.nextTransactionSize + } + + NotificationProcessingState.COMPLETED -> { + if (shouldUpdateFeeBumpFunctions()) { + preloadFeeData.runForced() + } + } + else -> { + // ignore + } + } + }.subscribe() + .also { subscription -> compositeSubscription.add(subscription) } + } + + private fun stopObservingNtsChanges() { + compositeSubscription.clear() + } + + private fun shouldUpdateFeeBumpFunctions(): Boolean { + nextTransactionSizeRepository.nextTransactionSize?.let { currentNts -> + val newUnconfirmedUtxos = currentNts.unconfirmedUtxos + if (newUnconfirmedUtxos.isNotEmpty()) { + val previousUnconfirmedUtxos = initialNts?.unconfirmedUtxos + return newUnconfirmedUtxos != previousUnconfirmedUtxos + } + return false + } + return false + } +} \ No newline at end of file diff --git a/android/apollo/src/test/java/io/muun/apollo/domain/action/PreloadFeeDataActionTest.kt b/android/apollo/src/test/java/io/muun/apollo/domain/action/PreloadFeeDataActionTest.kt new file mode 100644 index 00000000..42b062ed --- /dev/null +++ b/android/apollo/src/test/java/io/muun/apollo/domain/action/PreloadFeeDataActionTest.kt @@ -0,0 +1,105 @@ +package io.muun.apollo.domain.action + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.muun.apollo.BaseTest +import io.muun.apollo.TestUtils +import io.muun.apollo.data.external.Gen +import io.muun.apollo.data.net.HoustonClient +import io.muun.apollo.data.preferences.FeeWindowRepository +import io.muun.apollo.data.preferences.MinFeeRateRepository +import io.muun.apollo.data.preferences.TransactionSizeRepository +import io.muun.apollo.domain.action.realtime.PreloadFeeDataAction +import io.muun.apollo.domain.libwallet.LibwalletService +import io.muun.apollo.domain.model.MuunFeature +import io.muun.apollo.domain.model.RealTimeFees +import io.muun.apollo.domain.selector.FeatureSelector +import io.muun.common.model.SizeForAmount +import io.muun.common.model.UtxoStatus +import org.junit.Before +import org.junit.Test +import org.threeten.bp.ZonedDateTime +import rx.Observable + +class PreloadFeeDataActionTest: BaseTest() { + + private val feeWindowRepository = mockk(relaxed = true) + private val minFeeRateRepository = mockk(relaxed = true) + private val transactionSizeRepository = mockk(relaxed = true) + private val houstonClient = mockk() + private val featureSelector = mockk(relaxed = true) + private val libwalletService = mockk(relaxed = true) + + private lateinit var preloadFeeData: PreloadFeeDataAction + + val feeBumpFunctions = Gen.feeBumpFunctions() + val feeWindow = Gen.feeWindow() + val minFeeRateInWeightUnits = 0.25 + val minMempoolFeeRateInSatPerVbyte = minFeeRateInWeightUnits * 4 + val minFeeRateIncrementToReplaceByFeeInSatPerVbyte = 2.5 + + val realTimeFees = RealTimeFees( + feeBumpFunctions, + feeWindow, + minMempoolFeeRateInSatPerVbyte, + minFeeRateIncrementToReplaceByFeeInSatPerVbyte, + ZonedDateTime.now() + ) + + private val sizeProgressionWithUnconfirmedUtxos = SizeForAmount( + 1000L, + 240, + "default:0", + UtxoStatus.UNCONFIRMED, + 240, + "m/schema:1'/recovery:1'", + 1 + ) + + @Before + fun setUp() { + preloadFeeData = PreloadFeeDataAction( + houstonClient, + feeWindowRepository, + minFeeRateRepository, + transactionSizeRepository, + featureSelector, + libwalletService + ) + } + + @Test + fun testRunTwiceShouldRunOneTimeBecauseOfThrottling() { + every { houstonClient.fetchRealTimeFees(any()) }.returns(Observable.just(realTimeFees)) + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(sizeProgressionWithUnconfirmedUtxos)) + every { featureSelector.get(MuunFeature.EFFECTIVE_FEES_CALCULATION) }.returns(true) + + TestUtils.fetchItemFromObservable(preloadFeeData.action()) + + TestUtils.fetchItemFromObservable(preloadFeeData.action()) + + verify(exactly = 1) { feeWindowRepository.store(feeWindow) } + verify(exactly = 1) { minFeeRateRepository.store(minFeeRateInWeightUnits) } + verify(exactly = 1) { libwalletService.persistFeeBumpFunctions(feeBumpFunctions) } + + // TODO we should test throttling logic (e.g multiple calls in after a threshold should + // run action multiple times) + } + + @Test + fun testForceRunTwiceShouldCallServicesTwoTimes() { + every { houstonClient.fetchRealTimeFees(any()) }.returns(Observable.just(realTimeFees)) + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(sizeProgressionWithUnconfirmedUtxos)) + every { featureSelector.get(MuunFeature.EFFECTIVE_FEES_CALCULATION) }.returns(true) + + TestUtils.fetchItemFromObservable(preloadFeeData.runForced()) + TestUtils.fetchItemFromObservable(preloadFeeData.runForced()) + + verify(exactly = 2) { feeWindowRepository.store(feeWindow) } + verify(exactly = 2) { minFeeRateRepository.store(minFeeRateInWeightUnits) } + verify(exactly = 2) { libwalletService.persistFeeBumpFunctions(feeBumpFunctions) } + } +} \ No newline at end of file diff --git a/android/apollo/src/test/java/io/muun/apollo/domain/sync/FeeDataSyncerTest.kt b/android/apollo/src/test/java/io/muun/apollo/domain/sync/FeeDataSyncerTest.kt new file mode 100644 index 00000000..14cb0ff1 --- /dev/null +++ b/android/apollo/src/test/java/io/muun/apollo/domain/sync/FeeDataSyncerTest.kt @@ -0,0 +1,149 @@ +package io.muun.apollo.domain.sync + +import android.os.Handler +import android.os.Looper +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import io.muun.apollo.BaseTest +import io.muun.apollo.data.external.Gen +import io.muun.apollo.data.preferences.TransactionSizeRepository +import io.muun.apollo.domain.action.NotificationActions +import io.muun.apollo.domain.action.NotificationProcessingState +import io.muun.apollo.domain.action.realtime.PreloadFeeDataAction +import io.muun.apollo.domain.model.MuunFeature +import io.muun.apollo.domain.selector.FeatureSelector +import io.muun.common.model.SizeForAmount +import io.muun.common.model.UtxoStatus +import org.junit.After +import org.junit.Before +import org.junit.Test +import rx.subjects.PublishSubject + +class FeeDataSyncerTest: BaseTest() { + private val preloadFeeData = mockk(relaxed = true) + private val transactionSizeRepository = mockk(relaxed = true) + private val notificationActions = mockk(relaxed = true) + private val featureSelector = mockk(relaxed = true) + private val processingSubject: PublishSubject = PublishSubject.create() + private val processingObservable = processingSubject.asObservable() + + private lateinit var feeDataSyncer: FeeDataSyncer + + private val initialSizeProgressionWithUnconfirmedUtxos = SizeForAmount( + 1000L, + 240, + "a:0", + UtxoStatus.UNCONFIRMED, + 240, + "m/schema:1'/recovery:1'", + 1 + ) + + private val initialSizeProgressionWithConfirmedUtxos = SizeForAmount( + 1000L, + 240, + "a:0", + UtxoStatus.CONFIRMED, + 240, + "m/schema:1'/recovery:1'", + 1 + ) + + private val finalSizeProgressionWithUnconfirmedUtxos = SizeForAmount( + 1000L, + 240, + "b:0", + UtxoStatus.UNCONFIRMED, + 240, + "m/schema:1'/recovery:1'", + 1 + ) + + @Before + fun setUp() { + every { featureSelector.get(MuunFeature.EFFECTIVE_FEES_CALCULATION) }.returns(true) + every { notificationActions.getNotificationProcessingState() }.returns(processingObservable) + + mockkStatic(Looper::class) + every { Looper.getMainLooper() } returns mockk() + mockkConstructor(Handler::class) + every { anyConstructed().removeCallbacksAndMessages(any()) } returns Unit + every { anyConstructed().postDelayed(any(), any()) } returns true + + feeDataSyncer = FeeDataSyncer( + preloadFeeData = preloadFeeData, + nextTransactionSizeRepository = transactionSizeRepository, + notificationActions = notificationActions, + featureSelector = featureSelector + ) + } + + @After + fun teardown() { + unmockkAll() + } + + @Test + fun testWithNTSEmptyShouldNotCallForceRun() { + every { transactionSizeRepository.nextTransactionSize } + .returns(null) + + feeDataSyncer.enterForeground() + processingSubject.onNext(NotificationProcessingState.STARTED) + verify(exactly = 0) { preloadFeeData.runForced() } + + processingSubject.onNext(NotificationProcessingState.COMPLETED) + Thread.sleep(200) + verify(exactly = 0) { preloadFeeData.runForced() } + } + + @Test + fun testWithNTSNotEmptyAndNoChangesShouldNotCallForceRun() { + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(initialSizeProgressionWithUnconfirmedUtxos)) + + feeDataSyncer.enterForeground() + processingSubject.onNext(NotificationProcessingState.STARTED) + verify(exactly = 0) { preloadFeeData.runForced() } + + processingSubject.onNext(NotificationProcessingState.COMPLETED) + Thread.sleep(200) + verify(exactly = 0) { preloadFeeData.runForced() } + } + + @Test + fun testWithNTSChangesAndUnconfirmedStatusShouldCallForceRun() { + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(initialSizeProgressionWithUnconfirmedUtxos)) + + feeDataSyncer.enterForeground() + processingSubject.onNext(NotificationProcessingState.STARTED) + verify(exactly = 0) { preloadFeeData.runForced() } + + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(finalSizeProgressionWithUnconfirmedUtxos)) + processingSubject.onNext(NotificationProcessingState.COMPLETED) + Thread.sleep(200) + verify(exactly = 1) { preloadFeeData.runForced() } + } + + @Test + fun testWithNTSChangesAndConfirmedStatusShouldNotCallForceRun() { + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(initialSizeProgressionWithUnconfirmedUtxos)) + + feeDataSyncer.enterForeground() + processingSubject.onNext(NotificationProcessingState.STARTED) + verify(exactly = 0) { preloadFeeData.runForced() } + + every { transactionSizeRepository.nextTransactionSize } + .returns(Gen.nextTransactionSize(initialSizeProgressionWithConfirmedUtxos)) + processingSubject.onNext(NotificationProcessingState.COMPLETED) + Thread.sleep(200) + verify(exactly = 0) { preloadFeeData.runForced() } + } +} \ No newline at end of file diff --git a/android/apolloui/build.gradle b/android/apolloui/build.gradle index 1bd312f5..b89a637a 100644 --- a/android/apolloui/build.gradle +++ b/android/apolloui/build.gradle @@ -1,4 +1,5 @@ buildscript { + ext.nav_version = "2.7.7" // Next version upgrade requires bump minSdk to 21 dependencies { // Add third party gradle plugins @@ -88,14 +89,15 @@ android { buildFeatures { viewBinding true + buildConfig true } defaultConfig { applicationId "io.muun.apollo" minSdk 19 targetSdk 34 - versionCode 1205 - versionName "52.5" + versionCode 1206 + versionName "52.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/ @@ -274,12 +276,12 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() + jvmTarget = JavaVersion.VERSION_17.toString() } lint { @@ -288,6 +290,11 @@ android { textReport true lintConfig file("$rootDir/linters/android-lint/config.xml") baseline file('lint-baseline.xml') + + // Workaround linter bug regarding our addition of test sources (e.g apollo lib). Reported + // issues have broken location since apollo test sources aren't inside this module. + // TODO fix this + ignoreTestSources true } packagingOptions { @@ -306,11 +313,6 @@ android { exclude 'META-INF/rxjava.properties' } - dexOptions { - preDexLibraries true - dexInProcess = true - } - // See: http://tools.android.com/tech-docs/unit-testing-support#TOC-Method-...-not-mocked.- testOptions { unitTests.returnDefaultValues = true diff --git a/android/apolloui/lint-baseline.xml b/android/apolloui/lint-baseline.xml index 0342d348..4eb0da22 100644 --- a/android/apolloui/lint-baseline.xml +++ b/android/apolloui/lint-baseline.xml @@ -1,417 +1,202 @@ - + - - - - + id="CheckResult" + message="The result of `title` is not used" + errorLine1=" builder.title(R.string.error_send_report_dialog_title);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java" + line="529" + column="13"/> + id="CheckResult" + message="The result of `message` is not used" + errorLine1=" builder.message(R.string.error_send_report_dialog_body);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java" + line="530" + column="13"/> + id="CheckResult" + message="The result of `message` is not used" + errorLine1=" builder.message(R.string.error_send_report_dialog_body_short);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/base/BasePresenter.java" + line="533" + column="13"/> + id="DefaultLocale" + message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." + errorLine1=" super.setQrContent(invoice.original, invoice.original.toUpperCase())" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/ln/LnInvoiceQrFragment.kt" + line="123" + column="63"/> + message="Implicitly using the default locale is a common source of bugs: Use `toUpperCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." + errorLine1=" val linkText = getString(R.string.see_in_node_explorer).toUpperCase()" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/new_operation/NewOperationActivity.kt" + line="697" + column="65"/> + message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead" + errorLine1=" return String.format("%d:%02d:%02d", hours, minutes, seconds)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/utils/ReceiveLnInvoiceFormatter.kt" + line="18" + column="16"/> + message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead" + errorLine1=" String.format("%s (+%d)", countryInfo.countryName, countryInfo.countryNumber)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_country/SelectCountryActivity.java" + line="100" + column="21"/> + errorLine1=" return currency.getCode().toLowerCase().contains(query.toLowerCase())" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_currency/SelectCurrencyActivity.java" + line="236" + column="39"/> + errorLine1=" return currency.getCode().toLowerCase().contains(query.toLowerCase())" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_currency/SelectCurrencyActivity.java" + line="236" + column="68"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + errorLine1=" || currency.getName().toLowerCase().contains(query.toLowerCase());" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_currency/SelectCurrencyActivity.java" + line="237" + column="72"/> - - - - - - - - + errorLine1=" return context.getString(R.string.long_date_format, date, time.toLowerCase(), timeZone);" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/utils/UiUtils.java" + line="412" + column="72"/> + id="KotlinNullnessAnnotation" + message="Do not use `@NotNull` in Kotlin; the nullability is already implied by the Kotlin type `Operation` **not** ending with `?`" + errorLine1=" override fun showNewOperationNotification(@NotNull operation: Operation) {" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/app/NotificationServiceImpl.kt" + line="132" + column="47"/> + id="KotlinNullnessAnnotation" + message="Do not use `@NotNull` in Kotlin; the nullability is already implied by the Kotlin type `Contact` **not** ending with `?`" + errorLine1=" override fun showNewContactNotification(@NotNull contact: Contact) {" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/app/NotificationServiceImpl.kt" + line="148" + column="45"/> + id="KotlinNullnessAnnotation" + message="Do not use `@NotNull` in Kotlin; the nullability is already implied by the Kotlin type `Bundle` **not** ending with `?`" + errorLine1=" override fun setUp(@NotNull arguments: Bundle) {" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_amount/SelectAmountPresenter.kt" + line="49" + column="24"/> + id="KotlinNullnessAnnotation" + message="Do not use `@NotNull` in Kotlin; the nullability is already implied by the Kotlin type `Bundle` **not** ending with `?`" + errorLine1=" override fun setUp(@NotNull arguments: Bundle) {" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_bitcoin_unit/SelectBitcoinUnitPresenter.kt" + line="18" + column="24"/> + id="KotlinNullnessAnnotation" + message="Do not use `@NotNull` in Kotlin; the nullability is already implied by the Kotlin type `Bundle` **not** ending with `?`" + errorLine1=" override fun setUp(@NotNull arguments: Bundle) {" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_night_mode/SelectNightModePresenter.kt" + line="17" + column="24"/> + id="KotlinNullnessAnnotation" + message="Do not use `@NotNull` in Kotlin; the nullability is already implied by the Kotlin type `Bundle` **not** ending with `?`" + errorLine1=" override fun setUp(@NotNull arguments: Bundle) {" + errorLine2=" ~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/recovery_code/show/ShowRecoveryCodePresenter.kt" + line="19" + column="24"/> @@ -465,7 +250,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -542,10 +327,21 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + @@ -586,7 +382,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -597,7 +393,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -608,7 +404,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -619,7 +415,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -630,7 +426,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -652,7 +448,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -663,10 +459,21 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + @@ -685,7 +492,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -696,7 +503,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -707,7 +514,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -740,7 +547,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -751,7 +558,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -762,7 +569,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -773,7 +580,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -795,7 +602,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -806,7 +613,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -817,7 +624,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -828,7 +635,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -839,7 +646,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -850,7 +657,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -861,7 +668,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -872,7 +679,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -883,7 +690,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -894,7 +701,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -905,7 +712,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -916,7 +723,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -927,7 +734,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -938,7 +745,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -960,7 +767,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -971,7 +778,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -982,7 +789,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -992,8 +799,8 @@ errorLine1=" @BindView(R.id.recovery_code_condition_1)" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1003,8 +810,8 @@ errorLine1=" @BindView(R.id.recovery_code_condition_2)" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1014,55 +821,11 @@ errorLine1=" @BindView(R.id.recovery_code_accept)" errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - @@ -1257,7 +998,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -1268,7 +1009,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1279,7 +1020,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> @@ -1290,7 +1031,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -1301,7 +1042,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~"> @@ -1312,7 +1053,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1323,7 +1064,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1334,65 +1075,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - + errorLine1=" @BindView(R.id.currency_item_logo)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.currency_item_label)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.currency_item_selected_icon)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.debug_text_server_address)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.debug_button_fund_wallet_onchain)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.debug_button_fund_wallet_offchain)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.debug_button_generate_blocks)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.debug_button_drop_tx)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="43" + column="15"/> + errorLine1=" @BindView(R.id.debug_button_undrop_tx)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + line="46" + column="15"/> + errorLine1=" @BindView(R.id.debug_switch_allow_multi_session)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + + + + @@ -1587,7 +1317,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1598,7 +1328,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1609,7 +1339,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1620,7 +1350,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1631,7 +1361,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~"> @@ -1642,7 +1372,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -1653,7 +1383,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -1664,7 +1394,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1675,7 +1405,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -1686,7 +1416,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -1697,7 +1427,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -1822,17 +1552,6 @@ column="15"/> - - - - @@ -1851,7 +1570,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -2016,7 +1735,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2027,7 +1746,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2038,7 +1757,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2049,21 +1768,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - @@ -2082,7 +1790,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -2093,7 +1801,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2104,7 +1812,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -2181,7 +1889,7 @@ errorLine2=" ~~~~~~~~~~~~~~"> @@ -2192,7 +1900,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2203,7 +1911,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -2214,7 +1922,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2225,7 +1933,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2478,7 +2186,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -2504,17 +2212,6 @@ column="15"/> - - - - + errorLine1=" @BindView(R.id.home_taproot_card)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_security_center_card)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_high_fees_card)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_block_clock)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.login_start)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_start)" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/landing/LandingFragment.kt" + line="34" + column="15"/> + errorLine1=" @BindView(R.id.animation_view)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/landing/LandingFragment.kt" + line="37" + column="15"/> @@ -2676,7 +2373,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2687,7 +2384,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2698,29 +2395,40 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.receive_preference_section)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.receive_preference_item)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + @@ -2731,7 +2439,18 @@ errorLine2=" ~~~~~~~~~~~~~~~"> + + + + @@ -2742,7 +2461,7 @@ errorLine2=" ~~~~~~~~~~~~~~~"> @@ -2753,7 +2472,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -2764,7 +2483,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2775,7 +2494,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2786,7 +2505,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> @@ -2797,7 +2516,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -2808,169 +2527,92 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.high_fees_warning_overlay)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.loading_view_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/loading/LoadingFragment.kt" + line="25" + column="15"/> + errorLine1=" @BindView(R.id.loading_view_spinner)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.loading_view_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.loading_view_desc)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_waiting_for_email_open_email_client)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_waiting_for_email_verification_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/login_authorize/LoginAuthorizeFragment.java" + line="25" + column="15"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + errorLine1=" @BindView(R.id.signup_waiting_for_email_verification_explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3039,7 +2681,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3050,7 +2692,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3061,7 +2703,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -3072,18 +2714,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - - - - @@ -3094,7 +2725,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> @@ -3105,7 +2736,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3116,7 +2747,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -3127,21 +2758,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~"> - - - - @@ -3281,43 +2901,10 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~"> - - - - - - - - - - - - @@ -3622,7 +3209,7 @@ errorLine2=" ~~~~~~~~~"> @@ -3699,7 +3286,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> @@ -3725,6 +3312,28 @@ column="16"/> + + + + + + + + + errorLine1=" @BindView(R.id.title)" + errorLine2=" ~~~~~~~~~~"> + errorLine1=" @BindView(R.id.description)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/new_op_error/NewOperationErrorFragment.kt" + line="47" + column="15"/> + errorLine1=" @BindView(R.id.insuffient_funds_extras)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/new_op_error/NewOperationErrorFragment.kt" + line="50" + column="15"/> + errorLine1=" @BindView(R.id.insuffient_funds_extras_amount)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/new_op_error/NewOperationErrorFragment.kt" + line="53" + column="15"/> + errorLine1=" @BindView(R.id.insuffient_funds_extras_balance)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/new_op_error/NewOperationErrorFragment.kt" + line="56" + column="15"/> + errorLine1=" @OnClick(R.id.exit)" + errorLine2=" ~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/new_op_error/NewOperationErrorFragment.kt" + line="154" + column="14"/> + errorLine1=" @BindView(R.id.priming_notifications_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.priming_notifications_desc)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.priming_notifications_enable)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.priming_notifications_skip)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.enter_old_password_input)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.use_recovery_code)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.change_password_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_profile_picture)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/operation_detail/OperationDetailActivity.java" + line="43" + column="15"/> + errorLine1=" @BindView(R.id.operation_detail_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_subtitle)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_description)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_status)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_notice_banner)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_date)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_amount)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_normal_section)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_confirmations)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_fee)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_address)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_txid)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_swap_section)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_swap_invoice)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_swap_hash)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_swap_preimage)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.operation_detail_swap_receiver_pubkey)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_operations_item_profile_picture)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_operations_item_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_operations_item_text_amount)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_operations_item_text_description)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/adapter/holder/OperationViewHolder.java" + line="25" + column="15"/> + errorLine1=" @BindView(R.id.home_operations_item_text_time)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.empty_screen)" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.home_operations_recycler_operation_list)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_phone_number_edit_local_number)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_phone_number_edit_country_prefix)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_phone_number_country_picker)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @OnClick(R.id.signup_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/phone_number/PhoneNumberFragment.java" + line="115" + column="14"/> + errorLine1=" @BindView(R.id.title)" + errorLine2=" ~~~~~~~~~~"> + errorLine1=" @BindView(R.id.description)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindColor(R.color.text_primary_color)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/PickerCard.kt" + line="25" + column="16"/> + errorLine1=" @BindColor(R.color.picker_disabled_color)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/PickerCard.kt" + line="29" + column="16"/> + errorLine1=" @BindColor(R.color.background)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/PopupWindow.java" + line="26" + column="16"/> + errorLine1=" @BindDrawable(R.drawable.pre_lollipop_popup_background)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/PopupWindow.java" + line="29" + column="19"/> + errorLine1=" @BindView(R.id.priming_recovery_code_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.priming_recovery_code_desc)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.priming_recovery_code_start)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_profile_explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_profile_picture)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_profile_edit_first_name)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_profile_edit_last_name)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @OnClick(R.id.signup_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/profile/ProfileFragment.java" + line="64" + column="14"/> + errorLine1=" @BindView(R.id.show_qr_image_qr)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_content)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_copy)" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_share)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.edit_amount_item)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindDimen(R.dimen.qr_code_size)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/QrFragment.kt" + line="45" + column="16"/> + errorLine1=" @BindView(R.id.picker_radio_group)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.open_email_client)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.rc_login_email_auth_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.rc_login_email_auth_description)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.rc_login_email_auth_icon)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.rc_login_email_auth_loading)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.rc_only_login_whats_this)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/rc_only_login/RcOnlyLoginFragment.kt" + line="17" + column="15"/> + errorLine1=" @BindView(R.id.rc_only_login_recovery_code_box)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/rc_only_login/RcOnlyLoginFragment.kt" + line="20" + column="15"/> + errorLine1=" @BindView(R.id.rc_only_login_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/rc_only_login/RcOnlyLoginFragment.kt" + line="23" + column="15"/> + errorLine1=" @BindView(R.id.receive_preference_value)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.fee_options_message)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.fee_option_fast)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.fee_option_medium)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.fee_option_slow)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> - + + errorLine1=" @BindView(R.id.status_message)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.confirm_fee)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindString(R.string.fee_options_message)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/recommended_fee/RecommendedFeeFragment.kt" + line="48" + column="17"/> + errorLine1=" @BindString(R.string.fee_options_whats_this)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/recommended_fee/RecommendedFeeFragment.kt" + line="51" + column="17"/> + errorLine1=" @BindView(R.id.enter_recovery_code_box)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.enter_recovery_code_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.header)" + errorLine2=" ~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.empty_screen)" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/scan_qr/ScanQrActivity.java" + line="54" + column="15"/> + errorLine1=" @BindView(R.id.scan_qr_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.scan_qr_scanner)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.scan_qr_subtitle)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.scan_qr_frame_background)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.uri_paster)" + errorLine2=" ~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.paste_from_clipboard)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.section_header_item_label)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.tag_email_skipped)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.task_email)" + errorLine2=" ~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.task_recovery_code)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.task_export_keys)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.progress)" + errorLine2=" ~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.progress_bar_subtitle)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.sc_success_box)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.button_export_keys_again)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @OnClick(R.id.security_logout_sign_in)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/security_logout/SecurityLogoutActivity.java" + line="39" + column="14"/> + errorLine1=" @BindView(R.id.header)" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_country/SelectCountryActivity.java" + line="37" + column="15"/> + errorLine1=" @BindView(R.id.list)" + errorLine2=" ~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_country/SelectCountryActivity.java" + line="40" + column="15"/> + errorLine1=" @BindView(R.id.select_currency_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.select_currency_list)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindColor(R.color.icon_color)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_currency/SelectCurrencyActivity.java" + line="56" + column="16"/> + errorLine1=" @BindString(R.string.select_currency_most_popular_section_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_currency/SelectCurrencyActivity.java" + line="59" + column="17"/> + errorLine1=" @BindString(R.string.select_currency_all_section_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/select_currency/SelectCurrencyActivity.java" + line="62" + column="17"/> + errorLine1=" @BindView(R.id.set_up_pin_input)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.back_arrow)" + errorLine2=" ~~~~~~~~~~~~~~~"> + errorLine1=" @BindString(R.string.pin_error_on_setup_cancel)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + errorLine1=" @BindView(R.id.settings_username)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_header_section)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.wallet_details_section)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_edit_username)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_password)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_phone_number)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_bitcoin_unit)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_primary_currency)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_lightning)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.recovery_section)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_logout)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_delete_wallet)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.settings_version_code)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/settings/SettingsFragment.kt" + line="91" + column="15"/> + errorLine1=" @BindView(R.id.setup_p2p_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.setup_password_accept_subtitle)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.setup_password_accept_condition_1)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.setup_password_accept_condition_2)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/setup_password_accept/SetupPasswordAcceptFragment.kt" + line="24" + column="15"/> + errorLine1=" @BindView(R.id.setup_password_accept_action)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.setup_password_intro_info_box)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.create_email_skip)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.setup_password_intro_action)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.setup_password_success_action)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_tab)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_viewpager)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/ShowQrActivity.java" + line="57" + column="15"/> - - - - - - - - + errorLine1=" @BindView(R.id.show_qr_viewpager_container)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_unified_qr_container)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.show_qr_notifications_priming)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.recovery_code_box)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.recovery_code_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.unified_qr_scrollView)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5851,10 +5449,10 @@ + errorLine1=" @BindView(R.id.unified_qr_notifications_priming)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5862,10 +5460,10 @@ + errorLine1=" @BindView(R.id.qr_overlay)" + errorLine2=" ~~~~~~~~~~~~~~~"> @@ -5873,10 +5471,10 @@ + errorLine1=" @BindView(R.id.unified_qr_settings)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5884,10 +5482,10 @@ + errorLine1=" @BindView(R.id.unified_qr_settings_content)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -5895,1233 +5493,1376 @@ + errorLine1=" @BindView(R.id.expiration_time_item)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.unified_qr_loading)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.unified_qr_invoice_expired_overlay)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/unified/ShowUnifiedQrFragment.kt" + line="58" + column="15"/> + errorLine1=" @BindView(R.id.create_other_invoice)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.high_fees_warning_overlay)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.simple_message_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.simple_message_text)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.title)" + errorLine2=" ~~~~~~~~~~"> + errorLine1=" @BindView(R.id.subtitle)" + errorLine2=" ~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.body)" + errorLine2=" ~~~~~~~~~"> + errorLine1=" @BindView(R.id.action_button)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + errorLine1=" @OnClick(R.id.action_button)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/single_action/SingleActionActivity.java" + line="101" + column="14"/> + errorLine1=" @BindView(R.id.single_action_image)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.single_action_message)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.single_action_description)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.single_action_action)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.change_password_start_explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.change_password_start)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @OnClick(R.id.change_password_start)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/settings/edit_password/StartPasswordChangeFragment.kt" + line="37" + column="14"/> + errorLine1=" @BindView(R.id.message_image)" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.message_text)" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindDrawable(R.drawable.ic_baseline_warning_24px)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/StatusMessage.java" + line="35" + column="19"/> + errorLine1=" @BindColor(R.color.warning_color)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/StatusMessage.java" + line="38" + column="16"/> + errorLine1=" @BindDrawable(R.drawable.error_badge)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/StatusMessage.java" + line="41" + column="19"/> + errorLine1=" @BindColor(R.color.error_color)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/view/StatusMessage.java" + line="44" + column="16"/> + errorLine1=" @BindView(R.id.sync_contacts_explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.initial_sync_loading)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindString(R.string.signup_sync_creating)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/sync/SyncFragment.java" + line="21" + column="17"/> + errorLine1=" @BindString(R.string.signup_sync_loading)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/sync/SyncFragment.java" + line="24" + column="17"/> + errorLine1=" @BindView(R.id.description)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.taproot_success_illustration_default)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/tr_success/TaprootSuccessFragment.kt" + line="16" + column="15"/> + errorLine1=" @BindView(R.id.taproot_success_illustration_active)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.block_clock)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.description)" + errorLine2=" ~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.confirm)" + errorLine2=" ~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.muun_action_drawer_description)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/new_operation/TitleAndDescriptionDrawer.java" + line="36" + column="19"/> + errorLine1=" @BindView(R.id.drawer_unified_qr_header)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/UnifiedQrFullContentDialogFragment.kt" + line="54" + column="19"/> + errorLine1=" @BindView(R.id.drawer_unified_qr_address)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/UnifiedQrFullContentDialogFragment.kt" + line="57" + column="19"/> + errorLine1=" @BindView(R.id.drawer_unified_qr_address_copy_clickable_area)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/UnifiedQrFullContentDialogFragment.kt" + line="60" + column="19"/> + errorLine1=" @BindView(R.id.drawer_unified_qr_invoice)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/UnifiedQrFullContentDialogFragment.kt" + line="63" + column="19"/> + errorLine1=" @BindView(R.id.drawer_unified_qr_invoice_copy_clickable_area)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/show_qr/UnifiedQrFullContentDialogFragment.kt" + line="66" + column="19"/> + errorLine1=" @BindView(R.id.rc_unverified_warning_button)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_verification_text_explanation)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_verification_text_code)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_verification_resend)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.signup_verification_countdown)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindString(R.string.signup_verification_resend_countdown)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verification_code/VerificationCodeFragment.java" + line="53" + column="17"/> + errorLine1=" @BindString(R.string.action_resend_sms)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verification_code/VerificationCodeFragment.java" + line="56" + column="17"/> + errorLine1=" @BindString(R.string.action_call_me)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verification_code/VerificationCodeFragment.java" + line="59" + column="17"/> + errorLine1=" @BindString(R.string.action_change_num)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verification_code/VerificationCodeFragment.java" + line="62" + column="17"/> + errorLine1=" @OnClick(R.id.signup_verification_resend)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verification_code/VerificationCodeFragment.java" + line="126" + column="14"/> + errorLine1=" @OnClick(R.id.signup_continue)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verification_code/VerificationCodeFragment.java" + line="135" + column="14"/> + errorLine1=" @BindView(R.id.open_email_client)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verify_email/VerifyEmailFragment.kt" + line="18" + column="15"/> + errorLine1=" @BindView(R.id.verify_email_title)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.verify_email_description)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verify_email/VerifyEmailFragment.kt" + line="24" + column="15"/> + errorLine1=" @BindView(R.id.verify_email_icon)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.recovery_code_box)" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> + errorLine1=" @BindView(R.id.accept)" + errorLine2=" ~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/recovery_code/verify/VerifyRecoveryCodeFragment.java" + line="23" + column="15"/> + message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements" + errorLine1=" case R.layout.v_item_currency:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/adapter/holder/ViewHolderFactory.java" + line="35" + column="18"/> + message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements" + errorLine1=" case R.layout.v_item_section_header:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/adapter/holder/ViewHolderFactory.java" + line="38" + column="18"/> + message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements" + errorLine1=" case R.layout.home_contacts_item:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/adapter/holder/ViewHolderFactory.java" + line="41" + column="18"/> + message="Resource IDs will be non-final by default in Android Gradle Plugin version 8.0, avoid using them in switch case statements" + errorLine1=" case R.layout.home_operations_item:" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + + + + + + + + + + column="5"/> + id="VectorRaster" + message="This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable" + errorLine1=" <clip-path" + errorLine2=" ^"> + file="src/main/res/drawable/block_clock_taproot.xml" + line="37" + column="5"/> + id="VectorRaster" + message="This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable" + errorLine1=" <clip-path" + errorLine2=" ^"> + file="src/main/res/drawable-night/block_clock_taproot.xml" + line="46" + column="5"/> + id="VectorRaster" + message="This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable" + errorLine1=" <clip-path" + errorLine2=" ^"> + file="src/main/res/drawable/block_clock_taproot.xml" + line="46" + column="5"/> + id="VectorRaster" + message="This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable" + errorLine1=" <clip-path" + errorLine2=" ^"> + file="src/main/res/drawable-night/block_clock_taproot.xml" + line="55" + column="5"/> + id="VectorRaster" + message="This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable" + errorLine1=" <clip-path" + errorLine2=" ^"> + file="src/main/res/drawable/block_clock_taproot.xml" + line="55" + column="5"/> + id="VectorRaster" + message="This tag is not supported in images generated from this vector icon for API < 21; check generated icon to make sure it looks acceptable" + errorLine1=" <clip-path" + errorLine2=" ^"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id="GradleDependency" + message="A newer version of com.google.firebase:firebase-crashlytics-gradle than 2.9.6 is available: 3.0.2" + errorLine1=" classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.6'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="7" + column="19"/> + id="GradleDependency" + message="A newer version of com.google.android.material:material than 1.2.1 is available: 1.12.0" + errorLine1=" implementation 'com.google.android.material:material:1.2.1'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="361" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.constraintlayout:constraintlayout than 1.1.3 is available: 2.2.0" + errorLine1=" implementation 'androidx.constraintlayout:constraintlayout:1.1.3'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="362" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.recyclerview:recyclerview than 1.0.0 is available: 1.3.2" + errorLine1=" implementation 'androidx.recyclerview:recyclerview:1.0.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="363" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.preference:preference than 1.0.0 is available: 1.2.1" + errorLine1=" implementation 'androidx.preference:preference:1.0.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="365" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.emoji2:emoji2 than 1.1.0 is available: 1.5.0" + errorLine1=" implementation "androidx.emoji2:emoji2:$emojiVersion"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="367" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.multidex:multidex than 2.0.0 is available: 2.0.1" + errorLine1=" implementation 'androidx.multidex:multidex:2.0.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="371" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.browser:browser than 1.0.0 is available: 1.8.0" + errorLine1=" implementation 'androidx.browser:browser:1.0.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="373" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.lifecycle:lifecycle-runtime than 2.6.2 is available: 2.8.7" + errorLine1=" implementation "androidx.lifecycle:lifecycle-runtime:$version_lifecycle"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="377" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.lifecycle:lifecycle-process than 2.6.2 is available: 2.8.7" + errorLine1=" implementation "androidx.lifecycle:lifecycle-process:$version_lifecycle"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="379" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.webkit:webkit than 1.1.0 is available: 1.12.1" + errorLine1=" implementation 'androidx.webkit:webkit:1.1.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="382" + column="20"/> + id="GradleDependency" + message="A newer version of com.github.bumptech.glide:glide than 4.6.1 is available: 4.15.1" + errorLine1=" implementation 'com.github.bumptech.glide:glide:4.6.1'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="390" + column="20"/> + id="GradleDependency" + message="A newer version of com.google.zxing:core than 3.3.0 is available: 3.4.1" + errorLine1=" implementation 'com.google.zxing:core:3.3.0' // For showing QR codes" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="394" + column="20"/> + id="GradleDependency" + message="A newer version of org.mockito:mockito-core than 2.8.47 is available: 3.10.0" + errorLine1=" testImplementation 'org.mockito:mockito-core:2.8.47'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="402" + column="24"/> + id="GradleDependency" + message="A newer version of org.assertj:assertj-core than 3.8.0 is available: 3.9.1" + errorLine1=" testImplementation 'org.assertj:assertj-core:3.8.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="404" + column="24"/> + id="GradleDependency" + message="A newer version of junit:junit than 4.12 is available: 4.13.2" + errorLine1=" testImplementation 'junit:junit:4.12'" + errorLine2=" ~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="405" + column="24"/> + id="GradleDependency" + message="A newer version of androidx.test:core than 1.4.0 is available: 1.6.1" + errorLine1=" androidTestImplementation 'androidx.test:core:1.4.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="415" + column="31"/> + id="GradleDependency" + message="A newer version of androidx.test:rules than 1.4.0 is available: 1.6.1" + errorLine1=" androidTestImplementation 'androidx.test:rules:1.4.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="416" + column="31"/> + id="GradleDependency" + message="A newer version of androidx.test.ext:junit than 1.1.3 is available: 1.2.1" + errorLine1=" androidTestImplementation 'androidx.test.ext:junit:1.1.3'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="417" + column="31"/> + id="GradleDependency" + message="A newer version of androidx.test:runner than 1.4.0 is available: 1.6.2" + errorLine1=" androidTestImplementation 'androidx.test:runner:1.4.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="418" + column="31"/> + id="GradleDependency" + message="A newer version of org.assertj:assertj-core than 3.8.0 is available: 3.9.1" + errorLine1=" androidTestImplementation 'org.assertj:assertj-core:3.8.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="419" + column="31"/> + id="GradleDependency" + message="A newer version of androidx.test.espresso:espresso-core than 3.5.1 is available: 3.6.1" + errorLine1=" androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="424" + column="31"/> + id="GradleDependency" + message="A newer version of androidx.test.uiautomator:uiautomator than 2.2.0 is available: 2.3.0" + errorLine1=" androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="427" + column="31"/> + id="GradleDependency" + message="A newer version of androidx.navigation:navigation-fragment than 2.7.7 is available: 2.8.5" + errorLine1=" implementation "androidx.navigation:navigation-fragment:$nav_version"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="433" + column="20"/> + id="GradleDependency" + message="A newer version of androidx.navigation:navigation-ui than 2.7.7 is available: 2.8.5" + errorLine1=" implementation "androidx.navigation:navigation-ui:$nav_version"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="build.gradle" + line="434" + column="20"/> + + + + + + + + + + + + + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1="open class LandingPresenter @Inject constructor(private val forceFetchFcm: ForceFetchFcmAction) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/landing/LandingPresenter.kt" + line="13" + column="76"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1="open class LandingPresenter @Inject constructor(private val forceFetchFcm: ForceFetchFcmAction) :" + errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/landing/LandingPresenter.kt" + line="13" + column="76"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" private final UseMuunLinkAction useMuunLinkAction;" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/launcher/LauncherPresenter.java" + line="24" + column="19"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" UseMuunLinkAction useMuunLinkAction," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/launcher/LauncherPresenter.java" + line="35" + column="30"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" private final UseMuunLinkAction useMuunLinkAction;" + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/login_authorize/LoginAuthorizePresenter.java" + line="31" + column="19"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" UseMuunLinkAction useMuunLinkAction," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/login_authorize/LoginAuthorizePresenter.java" + line="40" + column="36"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" private val useMuunLinkAction: UseMuunLinkAction," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/rc_only_login_auth/RcLoginEmailAuthorizePresenter.kt" + line="22" + column="36"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" private val useMuunLinkAction: UseMuunLinkAction," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/rc_only_login_auth/RcLoginEmailAuthorizePresenter.kt" + line="22" + column="36"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" private val useMuunLinkAction: UseMuunLinkAction," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verify_email/VerifyEmailPresenter.kt" + line="22" + column="36"/> + id="VisibleForTests" + message="This class should only be accessed from tests or within private scope" + errorLine1=" private val useMuunLinkAction: UseMuunLinkAction," + errorLine2=" ~~~~~~~~~~~~~~~~~"> + file="src/main/java/io/muun/apollo/presentation/ui/fragments/verify_email/VerifyEmailPresenter.kt" + line="22" + column="36"/> + id="PrivateResource" + message="Overriding `@string/retry` which is marked as private in androidx.navigation:navigation-dynamic-features-fragment:2.7.7. If deliberate, use tools:override="true", otherwise pick a different name." + errorLine1=" <string name="retry">Reintentar</string>" + errorLine2=" ~~~~~"> + file="src/main/res/values-es/strings.xml" + line="13" + column="19"/> + id="PrivateResource" + message="Overriding `@string/retry` which is marked as private in androidx.navigation:navigation-dynamic-features-fragment:2.7.7. If deliberate, use tools:override="true", otherwise pick a different name." + errorLine1=" <string name="retry">Retry</string>" + errorLine2=" ~~~~~"> + file="src/main/res/values/strings.xml" + line="17" + column="19"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7175,7 +7037,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7186,7 +7048,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7197,7 +7059,7 @@ errorLine2=" ^"> @@ -7208,7 +7070,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7219,11 +7081,11 @@ errorLine2=" ^"> @@ -7232,14 +7094,36 @@ id="TrustAllX509TrustManager" message="`checkClientTrusted` is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers"> + file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.http-client/google-http-client/1.45.0/63eb7643e01797d6aa67c75ae31ffcc6c365db18/google-http-client-1.45.0.jar"/> + file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/com.google.http-client/google-http-client/1.45.0/63eb7643e01797d6aa67c75ae31ffcc6c365db18/google-http-client-1.45.0.jar"/> + + + + + + + + @@ -7326,7 +7210,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -7348,7 +7232,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -7359,7 +7243,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -7374,6 +7258,17 @@ column="2"/> + + + + + + + + @@ -7392,7 +7298,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7403,7 +7309,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7414,7 +7320,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7425,29 +7331,29 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + message="Possible overdraw: Root element paints background `@color/new_home_tooltip` with a theme that also paints a background (inferred theme is `@style/AppTheme`)" + errorLine1=" android:background="@color/new_home_tooltip"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + file="src/main/res/layout/view_new_home_tooltip.xml" + line="8" + column="9"/> + message="Possible overdraw: Root element paints background `@color/background` with a theme that also paints a background (inferred theme is `@style/AppTheme`)" + errorLine1=" android:background="@color/background"" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7609,13 +7515,9 @@ + message="The resource `R.mipmap.ic_launcher_round` appears to be unused"> + file="src/main/res/mipmap-hdpi/ic_launcher_round.png"/> + + + + + + + + @@ -7692,7 +7616,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7703,7 +7627,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7714,7 +7638,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7725,7 +7649,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7736,7 +7660,18 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + + + + @@ -7747,7 +7682,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~"> @@ -7758,7 +7693,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7769,7 +7704,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7780,7 +7715,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7791,7 +7726,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7802,7 +7737,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7813,7 +7748,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7824,18 +7759,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> - - - - @@ -7864,19 +7788,52 @@ errorLine2=" ~~~~~~~~~~~~~~"> + errorLine1=" <LinearLayout" + errorLine2=" ~~~~~~~~~~~~"> + line="31" + column="18"/> + + + + + + + + + + + + @@ -7897,7 +7854,7 @@ errorLine2=" ~~~~~~~~~~"> @@ -7908,7 +7865,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -7919,7 +7876,7 @@ errorLine2=" ~~~~~~~~~~"> @@ -7930,7 +7887,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -7941,7 +7898,7 @@ errorLine2=" ~~~"> @@ -7952,13 +7909,13 @@ errorLine2=" ~~~"> + message="The following images appear both as density independent `.xml` files and as bitmap files: src/main/res/drawable-hdpi/ic_close.webp, src/main/res/drawable-night/ic_close.xml"> + + + + @@ -7980,10 +7948,10 @@ file="src/main/res/drawable-hdpi/ic_clock.png"/> - + + + + + + + + + + + + + + + + + @@ -8097,7 +8109,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8108,7 +8120,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8119,7 +8131,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8130,7 +8142,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8141,7 +8153,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8152,7 +8164,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8163,7 +8175,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8174,7 +8186,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8185,7 +8197,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8196,7 +8208,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8207,7 +8219,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8218,7 +8230,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8229,7 +8241,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8240,7 +8252,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8251,7 +8263,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8262,7 +8274,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8273,7 +8285,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8284,7 +8296,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8295,7 +8307,7 @@ errorLine2=" ~~~~~~~~~"> @@ -8306,120 +8318,10 @@ errorLine2=" ~~~~~~~~~"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -8438,7 +8340,7 @@ errorLine2=" ~~~~~~~~"> @@ -8449,7 +8351,7 @@ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~"> @@ -8460,7 +8362,7 @@ errorLine2=" ~~~~"> @@ -8471,103 +8373,4 @@ file="src/main/AndroidManifest.xml"/> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/apolloui/proguard/proguard-android.pro b/android/apolloui/proguard/proguard-android.pro index df16a347..cb1bbd63 100644 --- a/android/apolloui/proguard/proguard-android.pro +++ b/android/apolloui/proguard/proguard-android.pro @@ -65,4 +65,11 @@ # Couldn't find androidx.lifecycle.w.value # Couldn't reproduce but adding rule just in case # See https://github.com/mozilla-mobile/android-components/issues/6642 --keep class androidx.lifecycle.** {*;} \ No newline at end of file +-keep class androidx.lifecycle.** {*;} + +-dontwarn org.ietf.jgss.GSSContext +-dontwarn org.ietf.jgss.GSSCredential +-dontwarn org.ietf.jgss.GSSException +-dontwarn org.ietf.jgss.GSSManager +-dontwarn org.ietf.jgss.GSSName +-dontwarn org.ietf.jgss.Oid \ No newline at end of file diff --git a/android/apolloui/src/main/AndroidManifest.xml b/android/apolloui/src/main/AndroidManifest.xml index 7f49db0a..4617e9c0 100644 --- a/android/apolloui/src/main/AndroidManifest.xml +++ b/android/apolloui/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ +> 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 d6dfb3d1..423e373b 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 @@ -18,12 +18,15 @@ import io.muun.apollo.domain.ApplicationLockManager; import io.muun.apollo.domain.BackgroundTimesService; import io.muun.apollo.domain.NightModeManager; +import io.muun.apollo.domain.action.UserActions; +import io.muun.apollo.domain.action.realtime.PreloadFeeDataAction; import io.muun.apollo.domain.action.session.DetectAppUpdateAction; import io.muun.apollo.domain.analytics.Analytics; import io.muun.apollo.domain.analytics.AnalyticsEvent; import io.muun.apollo.domain.libwallet.LibwalletBridge; import io.muun.apollo.domain.model.NightMode; import io.muun.apollo.domain.selector.BitcoinUnitSelector; +import io.muun.apollo.domain.sync.FeeDataSyncer; import io.muun.apollo.presentation.app.di.ApplicationComponent; import io.muun.apollo.presentation.app.di.DaggerApplicationComponent; import io.muun.apollo.presentation.ui.utils.UserFacingErrorMessagesImpl; @@ -90,6 +93,15 @@ public abstract class ApolloApplication extends Application @Inject FeaturesRepository featuresRepository; + @Inject + PreloadFeeDataAction preloadFeeData; + + @Inject + FeeDataSyncer feeDataSyncer; + + @Inject + UserActions userActions; + @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); @@ -340,12 +352,18 @@ public void onTerminate() { public void onStart(@NonNull LifecycleOwner owner) { // app moved to foreground analytics.report(new AnalyticsEvent.E_APP_WILL_ENTER_FOREGROUND()); backgroundTimesService.enterForeground(); + + if (userActions.isLoggedIn()) { + preloadFeeData.run(); + feeDataSyncer.enterForeground(); + } } @Override public void onStop(@NonNull LifecycleOwner owner) { // app moved to background analytics.report(new AnalyticsEvent.E_APP_WILL_GO_TO_BACKGROUND()); backgroundTimesService.enterBackground(); + feeDataSyncer.enterBackground(); } @Override diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/activity/extension/MuunDialog.kt b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/activity/extension/MuunDialog.kt index abba73ac..b1e9be94 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/activity/extension/MuunDialog.kt +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/activity/extension/MuunDialog.kt @@ -5,12 +5,14 @@ import android.content.Context import android.content.DialogInterface import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup.LayoutParams import android.widget.TextView import androidx.annotation.IdRes import androidx.annotation.LayoutRes import androidx.annotation.StringRes import androidx.annotation.StyleRes import io.muun.apollo.R +import io.muun.apollo.presentation.ui.utils.UiUtils import io.muun.common.utils.Preconditions import rx.functions.Action0 import javax.annotation.CheckReturnValue @@ -21,6 +23,7 @@ class MuunDialog private constructor( private val layout: Int = 0, // By default, we'll use AlertDialog default layout private val dialogInit: MuunDialogInitializer? = null, private val style: Int = R.style.MuunAlertDialog, + private val fixedWidthInDp: Int = 0, private val titleResId: Int = 0, private val title: CharSequence? = null, private val messageResId: Int = 0, @@ -39,6 +42,7 @@ class MuunDialog private constructor( private var layout: Int = 0 // By default, we'll use AlertDialog default layout private var dialogInit: MuunDialogInitializer? = null private var style: Int = R.style.MuunAlertDialog + private var fixedWidthInDp: Int = 0 private var titleResId: Int = 0 private var title: CharSequence? = null private var messageResId: Int = 0 @@ -64,6 +68,11 @@ class MuunDialog private constructor( this.dialogInit = dialogInit } + @CheckReturnValue + fun fixedWidthInDp(widthInDp: Int) = apply { + this.fixedWidthInDp = widthInDp + } + @CheckReturnValue fun style(@StyleRes style: Int) = apply { this.style = style } @@ -124,6 +133,7 @@ class MuunDialog private constructor( layout, dialogInit, style, + fixedWidthInDp, titleResId, title, messageResId, @@ -164,6 +174,16 @@ class MuunDialog private constructor( alertDialog.show() + // Workaround for nasty dialog width bug in foldable devices and tablets regarding + // welcome to Muun dialog (Relative layout is causing trouble? ConstraintLayout has same + // issue.) + if (fixedWidthInDp != 0) { + alertDialog.window!!.setLayout( + UiUtils.dpToPx(context, fixedWidthInDp), + LayoutParams.WRAP_CONTENT + ) + } + return alertDialog } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java index d66f57e6..3c7922f2 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/base/BaseActivity.java @@ -398,6 +398,7 @@ protected boolean shouldIgnoreBackAndExit() { // Ugly hack to "simulate home button press". We need to exit "smoothly", sending // it to the background, as when Home Button is pressed. This gets the job done. // See: https://stackoverflow.com/questions/21253303/exit-android-app-on-back-pressed + @SuppressWarnings("UnsafeImplicitIntentLaunch") final Intent intent = new Intent() .setAction(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/home/HomeActivity.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/home/HomeActivity.java index 583f7634..c4ef1e35 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/home/HomeActivity.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/home/HomeActivity.java @@ -162,6 +162,7 @@ public void navigateToSecurityCenter() { public void showWelcomeToMuunDialog() { final MuunDialog muunDialog = new MuunDialog.Builder() .layout(R.layout.dialog_welcome_to_muun) + .fixedWidthInDp(290) // Workaround for bug with foldables and tablets .style(R.style.MuunWelcomeDialog) .addOnClickAction(R.id.welcome_to_muun_cta, v -> dismissDialog()) .build(); 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 603bdde2..b947ef3d 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 @@ -7,6 +7,7 @@ import io.muun.apollo.domain.action.base.CombineLatestAsyncAction import io.muun.apollo.domain.action.operation.ResolveOperationUriAction import io.muun.apollo.domain.action.operation.SubmitPaymentAction import io.muun.apollo.domain.action.realtime.FetchRealTimeDataAction +import io.muun.apollo.domain.action.realtime.PreloadFeeDataAction import io.muun.apollo.domain.analytics.AnalyticsEvent import io.muun.apollo.domain.analytics.AnalyticsEvent.E_NEW_OP_ACTION_TYPE import io.muun.apollo.domain.analytics.AnalyticsEvent.E_NEW_OP_COMPLETED @@ -78,8 +79,9 @@ class NewOperationPresenter @Inject constructor( private val paymentContextSel: PaymentContextSelector, private val exchangeRateSel: ExchangeRateSelector, private val bitcoinUnitSel: BitcoinUnitSelector, - private val submitPaymentAction: SubmitPaymentAction, - private val resolveOperationUriAction: ResolveOperationUriAction, + private val submitPayment: SubmitPaymentAction, + private val resolveOperationUri: ResolveOperationUriAction, + private val preloadFeeData: PreloadFeeDataAction, ) : BasePresenter(), RecommendedFeeParentPresenter, ManualFeeParentPresenter, @@ -168,7 +170,7 @@ class NewOperationPresenter @Inject constructor( // Set up SubmitPaymentAction. Needs to be set up first (e.g first async action to be // subscribed), to avoid its ActionState (will initially fire an EMPTY action state) messing // with global handleStates handleLoading action/param. - submitPaymentAction + submitPayment .state .compose(handleStates(view::setLoading, this::handleError)) .doOnNext { operation: Operation -> @@ -177,14 +179,23 @@ class NewOperationPresenter @Inject constructor( .let(this::subscribeTo) // Set up RealTimeData and PaymentContext - CombineLatestAsyncAction(fetchRealTimeData, resolveOperationUriAction) + val basicActions = CombineLatestAsyncAction( + fetchRealTimeData.state, + resolveOperationUri.state + ) + + CombineLatestAsyncAction(basicActions.getState(), preloadFeeData.state) .getState() .compose(handleStates(view::setLoading, this::handleError)) - .doOnNext { (_, paymentRequest) -> + .doOnNext { (basicActionsPair, _) -> // Once we've updated exchange and fee rates, we can proceed with our preparations. // This is especially important if we landed here after the user clicked an external // link, since she skipped the home screen and didn't automatically fetch RTD. + // Both realTimeData and realtimeFees we just call the actions for its side-effects + // (aka refreshing the data in local storage). + val paymentRequest = basicActionsPair!!.second + // Note that RTD fetching is instantaneous if it was already up to date. paymentContextSel.watch() .first() @@ -478,10 +489,16 @@ class NewOperationPresenter @Inject constructor( fetchRealTimeData.run() // if it's already running (eg ran by home screen), no problem. + // runIfDataIsInvalidated checks if the fee bump functions in Libwallet are outdated + // (invalidated). + // If the data is invalidated: make an API call to refresh it. + // If the data is fresh: simply returns null to complete loading on this screen. + preloadFeeData.runIfDataIsInvalidated() + // 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), origin) + resolveOperationUri.run(OperationUri.fromString(uri), origin) view.setInitialBitcoinUnit(bitcoinUnitSel.get()) @@ -656,7 +673,7 @@ class NewOperationPresenter @Inject constructor( E_NEW_OP_SUBMITTED(*uncheckedConvert(opSubmittedMetadata(confirmStateViewModel))) ) - submitPaymentAction.run(preparedPayment) + submitPayment.run(preparedPayment) } catch (error: Throwable) { handleError(error) } diff --git a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_username/EditUsernameActivity.java b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_username/EditUsernameActivity.java index 77a11108..1dd87af6 100644 --- a/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_username/EditUsernameActivity.java +++ b/android/apolloui/src/main/java/io/muun/apollo/presentation/ui/settings/edit_username/EditUsernameActivity.java @@ -16,7 +16,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -import androidx.appcompat.view.menu.ActionMenuItemView; +import android.widget.TextView; import butterknife.BindColor; import butterknife.BindView; @@ -160,10 +160,11 @@ private void toggleMenuButtonEnabled(MenuItem item, boolean enabled) { item.setEnabled(enabled); final View itemView = findViewById(item.getItemId()); - if (itemView instanceof ActionMenuItemView) { + if (itemView instanceof TextView) { + // Ideally we would like to check if item is an ActionMenuItemView but is restrictedApi final int color = enabled ? getMenuItemEnabledColor() : disabledColor; - ((ActionMenuItemView) itemView).setTextColor(color); + ((TextView) itemView).setTextColor(color); } } diff --git a/android/apolloui/src/main/res/layout/home_activity.xml b/android/apolloui/src/main/res/layout/home_activity.xml index b47b0648..9cfe6d8a 100644 --- a/android/apolloui/src/main/res/layout/home_activity.xml +++ b/android/apolloui/src/main/res/layout/home_activity.xml @@ -1,7 +1,6 @@ androidHardwareAddresses; + + @Nullable + public String androidVbMeta; + + @Nullable + public String androidEfsCreationTimeInSeconds; + /** * Json constructor. */ @@ -257,7 +266,10 @@ public ClientJson( @Nullable final Integer androidEmachineArchitecture, @Nullable final Boolean androidSecurityEnhancedBuild, @Nullable final Boolean androidBridgeRootService, - @Nullable final Long androidAppSize + @Nullable final Long androidAppSize, + @Nullable final List androidHardwareAddresses, + @Nullable final String androidVbMeta, + @Nullable final String androidEfsCreationTimeInSeconds ) { this.type = type; this.buildType = buildType; @@ -301,5 +313,8 @@ public ClientJson( this.androidSecurityEnhancedBuild = androidSecurityEnhancedBuild; this.androidBridgeRootService = androidBridgeRootService; this.androidAppSize = androidAppSize; + this.androidHardwareAddresses = androidHardwareAddresses; + this.androidVbMeta = androidVbMeta; + this.androidEfsCreationTimeInSeconds = androidEfsCreationTimeInSeconds; } -} +} \ No newline at end of file diff --git a/common/src/main/java/io/muun/common/crypto/hd/DerivationPathUtils.java b/common/src/main/java/io/muun/common/crypto/hd/DerivationPathUtils.java index a5de7488..ad4dbb38 100644 --- a/common/src/main/java/io/muun/common/crypto/hd/DerivationPathUtils.java +++ b/common/src/main/java/io/muun/common/crypto/hd/DerivationPathUtils.java @@ -19,10 +19,13 @@ * *

For example, client-key/schema:1'/recovery:1'/qr:1/123 is equivalent to the BIP 32 derivation * path m/1'/1'/1/123 + * + *

The library also supports the `h` suffix instead of single quote `'` for hardened children. + * This is a common extension to BIP-32. */ public class DerivationPathUtils { - private static final String PATH_PATTERN = "^[a-zA-Z\\d\\-]+(/([a-zA-Z\\d\\-]+:)?\\d+'?)*$"; + private static final String PATH_PATTERN = "^[a-zA-Z\\d\\-]+(/([a-zA-Z\\d\\-]+:)?\\d+('|h)?)*$"; private static final Pattern PATH_REGEX = Pattern.compile(PATH_PATTERN); @@ -66,7 +69,7 @@ public static List parsePath(@NotNull String absolutePath) { for (int i = 1; i < pathParts.length; i += 1) { - final boolean isHardened = pathParts[i].endsWith("'"); + final boolean isHardened = pathParts[i].endsWith("'") || pathParts[i].endsWith("h"); String childString = pathParts[i]; diff --git a/common/src/main/java/io/muun/common/crypto/hd/PublicKeyTriple.java b/common/src/main/java/io/muun/common/crypto/hd/PublicKeyTriple.java index 9081a471..d8f5aa05 100644 --- a/common/src/main/java/io/muun/common/crypto/hd/PublicKeyTriple.java +++ b/common/src/main/java/io/muun/common/crypto/hd/PublicKeyTriple.java @@ -112,15 +112,29 @@ public PublicKeyPair toPair() { return new PublicKeyPair(userPublicKey, muunPublicKey); } + /** + * Ensures that all three derivation paths are equal. + */ private void checkDerivationPaths() { - final String userPath = userPublicKey.getAbsoluteDerivationPath(); - final String muunPath = muunPublicKey.getAbsoluteDerivationPath(); - final String swapServerPath = swapServerPublicKey.getAbsoluteDerivationPath(); + checkDerivationPaths(userPublicKey.getAbsoluteDerivationPath()); + } - Preconditions.checkArgument(userPath.equals(muunPath)); - Preconditions.checkArgument(muunPath.equals(swapServerPath)); + /** + * Ensures that the triple is derived at the specified derivation path. + */ + public void checkDerivationPaths(final String derivationPath) { + Preconditions.checkArgument( + derivationPath.equals(userPublicKey.getAbsoluteDerivationPath()) + ); + Preconditions.checkArgument( + derivationPath.equals(muunPublicKey.getAbsoluteDerivationPath()) + ); + Preconditions.checkArgument( + derivationPath.equals(swapServerPublicKey.getAbsoluteDerivationPath()) + ); } + private void checkNetworkParameters() { final NetworkParameters userNetwork = userPublicKey.getNetworkParameters(); final NetworkParameters muunNetwork = muunPublicKey.getNetworkParameters(); diff --git a/common/src/main/java/io/muun/common/model/SizeForAmount.java b/common/src/main/java/io/muun/common/model/SizeForAmount.java index 7e194a10..f0a1f780 100644 --- a/common/src/main/java/io/muun/common/model/SizeForAmount.java +++ b/common/src/main/java/io/muun/common/model/SizeForAmount.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.Objects; import javax.validation.constraints.NotNull; @JsonInclude(JsonInclude.Include.NON_NULL) @@ -96,7 +97,8 @@ public boolean equals(Object o) { final SizeForAmount that = (SizeForAmount) o; - return (amountInSatoshis == that.amountInSatoshis && sizeInBytes == that.sizeInBytes); + return (amountInSatoshis == that.amountInSatoshis && sizeInBytes == that.sizeInBytes + && status == that.status && Objects.equals(outpoint, that.outpoint)); } /** diff --git a/common/src/main/java/io/muun/common/utils/LnInvoice.java b/common/src/main/java/io/muun/common/utils/LnInvoice.java index 9016bf94..896e0dbe 100644 --- a/common/src/main/java/io/muun/common/utils/LnInvoice.java +++ b/common/src/main/java/io/muun/common/utils/LnInvoice.java @@ -776,10 +776,12 @@ public static class Amount { public final String amountWithMillis; public final long amountInSatoshis; + public final BtcAmount btcAmount; private Amount(String amountWithMillis, long amountInSatoshis) { this.amountWithMillis = amountWithMillis; this.amountInSatoshis = amountInSatoshis; + this.btcAmount = BtcAmount.fromMilliSats(Long.parseLong(amountWithMillis)); } } diff --git a/common/src/main/java/io/muun/common/utils/Preconditions.java b/common/src/main/java/io/muun/common/utils/Preconditions.java index 79655c11..44ebaa99 100644 --- a/common/src/main/java/io/muun/common/utils/Preconditions.java +++ b/common/src/main/java/io/muun/common/utils/Preconditions.java @@ -185,6 +185,36 @@ public static T checkNotNull(T reference, @Nullable Object errorMessage) { return reference; } + /** + * Ensures that an string is not null and not empty. + * + * @param reference a string + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + @Nonnull + public static String checkNotNullOrEmpty(@Nullable String reference) { + if (reference == null || reference.isEmpty()) { + throw new IllegalArgumentException(); + } + return reference; + } + + /** + * Ensures that an string is not null and not empty. + * + * @param reference a string + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + @Nonnull + public static String checkNotNullOrEmpty(@Nullable String reference, String errorMessage) { + if (reference == null || reference.isEmpty()) { + throw new IllegalArgumentException(errorMessage); + } + return reference; + } + /** * If a condition is true, ensures that an object reference is not null. If it's false, ensure * that the reference is null. @@ -192,7 +222,6 @@ public static T checkNotNull(T reference, @Nullable Object errorMessage) { * @return the null reference that was validated * @throws IllegalArgumentException if {@code reference} is not null */ - public static T checkNotNullOnlyIf(@Nullable T reference, boolean condition) { if (condition) { return checkNotNull(reference); diff --git a/common/src/test/java/io/muun/common/crypto/hd/DerivationPathUtilsTest.java b/common/src/test/java/io/muun/common/crypto/hd/DerivationPathUtilsTest.java new file mode 100644 index 00000000..6f64f5f2 --- /dev/null +++ b/common/src/test/java/io/muun/common/crypto/hd/DerivationPathUtilsTest.java @@ -0,0 +1,63 @@ +package io.muun.common.crypto.hd; + +import io.muun.common.crypto.hd.exception.InvalidDerivationPathException; + +import org.junit.Test; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +public class DerivationPathUtilsTest { + + @Test + public void testTranslateToCanonicalPath() { + final String canonicalPath = + DerivationPathUtils.translateToCanonicalPath("m/schema:1'/recovery:1'/qr:1/123"); + + assertThat(canonicalPath).isEqualTo("m/1'/1'/1/123"); + } + + @Test + public void testParsePathValid() { + final List childNumbers = + DerivationPathUtils.parsePath("client-key/schema:1'/recovery:1'/qr:1/123"); + + assertThat(childNumbers).containsExactly( + new ChildNumber(1, true, "schema"), + new ChildNumber(1, true, "recovery"), + new ChildNumber(1, false, "qr"), + new ChildNumber(123, false, "") + ); + } + + @Test(expected = InvalidDerivationPathException.class) + public void testParsePathInvalid() { + DerivationPathUtils.parsePath("$%^&"); + } + + @Test + public void testIsValidPath() { + assertThat(DerivationPathUtils.isValidPath("m/1'/1'")).isTrue(); + assertThat(DerivationPathUtils.isValidPath("client-key/schema:1'/recovery:1'")).isTrue(); + assertThat(DerivationPathUtils.isValidPath("$%^&'")).isFalse(); + } + + @Test + public void testHandlesPathsWithAlternativeHardenedSuffix() { + final String path = "m/84h/1h/0h/0/28h"; + + final List childNumbers = DerivationPathUtils.parsePath(path); + + assertThat(childNumbers).containsExactly( + new ChildNumber(84, true, ""), + new ChildNumber(1, true, ""), + new ChildNumber(0, true, ""), + new ChildNumber(0, false, ""), + new ChildNumber(28, true, "") + ); + + assertThat(DerivationPathUtils.isValidPath(path)).isTrue(); + } +} \ No newline at end of file diff --git a/common/src/test/java/io/muun/common/utils/LnInvoiceTest.java b/common/src/test/java/io/muun/common/utils/LnInvoiceTest.java index 88725612..ff331c2e 100644 --- a/common/src/test/java/io/muun/common/utils/LnInvoiceTest.java +++ b/common/src/test/java/io/muun/common/utils/LnInvoiceTest.java @@ -105,6 +105,9 @@ private void assertLnInvoice(LnInvoice invoice, LnInvoiceTestData.Values expecte if (expected.amountWithMillis != null) { assertThat(invoice.amount.amountWithMillis).isEqualTo(expected.amountWithMillis); assertThat(invoice.amount.amountInSatoshis).isEqualTo(expected.amountInSatoshis); + assertThat(invoice.amount.btcAmount.toMilliSats()).isEqualTo( + Long.parseLong(expected.amountWithMillis) + ); } if (expected.paymentSecret != null) { @@ -125,11 +128,12 @@ public void testEncodeDecode() { final long shortChannelId = RandomGenerator.getLong(); final String description = "Test!"; + final BtcAmount btcAmount = BtcAmount.fromSats(1000); final String rawInvoice = LnInvoice.encodeForTest( network, identityKey, paymentHash, - BtcAmount.fromSats(1000), + btcAmount, 144, description, publicNodeKey.getPublicKeyBytes(), @@ -142,6 +146,7 @@ public void testEncodeDecode() { final LnInvoice decoded = LnInvoice.decode(network, rawInvoice); assertEquals(1000, decoded.amount.amountInSatoshis); + assertEquals(btcAmount, decoded.amount.btcAmount); assertArrayEquals(paymentHash, Encodings.hexToBytes(decoded.id)); assertEquals(144L, decoded.cltvDelta.longValue()); assertEquals(description, decoded.description); diff --git a/gradle.properties b/gradle.properties index 0113f0df..7a800f46 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,6 +6,13 @@ org.gradle.configureondemand=true android.useAndroidX=true android.enableJetifier=true +# TODO: AGP 9.0 will remove this option. When that happens, Butterknife will stop working. +android.nonFinalResIds=false +# Keeps all attributes in one big bad R file, even those that are global to android +android.nonTransitiveRClass=false +# Run R8 in compat mode (which was the default in AGP 7.X) +# TODO: throughly test the without this flag and fix any R8 settings necessary +android.enableR8.fullMode=false # Hack for Icepick # Icepick uses now private APIs for annotation processing, this should open them up for it diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8fad3f5a..9a0a0e28 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/libwallet/aescbc/aescbc.go b/libwallet/aescbc/aescbc.go index c3ef0aff..37bf75f9 100755 --- a/libwallet/aescbc/aescbc.go +++ b/libwallet/aescbc/aescbc.go @@ -32,8 +32,8 @@ func EncryptNoPadding(key []byte, iv []byte, plaintext []byte) ([]byte, error) { return ciphertext, nil } -func DecryptPkcs7(key []byte, iv []byte, cypertext []byte) ([]byte, error) { - paddedPlaintext, err := DecryptNoPadding(key, iv, cypertext) +func DecryptPkcs7(key []byte, iv []byte, ciphertext []byte) ([]byte, error) { + paddedPlaintext, err := DecryptNoPadding(key, iv, ciphertext) if err != nil { return nil, err } @@ -41,7 +41,7 @@ func DecryptPkcs7(key []byte, iv []byte, cypertext []byte) ([]byte, error) { return pkcs7UnPadding(paddedPlaintext) } -func DecryptNoPadding(key []byte, iv []byte, cypertext []byte) ([]byte, error) { +func DecryptNoPadding(key []byte, iv []byte, ciphertext []byte) ([]byte, error) { if len(key) != KeySize { return nil, fmt.Errorf("invalid key size, expected %v, got %v", KeySize, len(key)) } @@ -50,10 +50,10 @@ func DecryptNoPadding(key []byte, iv []byte, cypertext []byte) ([]byte, error) { return nil, err } - plaintext := make([]byte, len(cypertext)) + plaintext := make([]byte, len(ciphertext)) mode := cipher.NewCBCDecrypter(block, iv) - mode.CryptBlocks(plaintext, cypertext) + mode.CryptBlocks(plaintext, ciphertext) return plaintext, nil } diff --git a/libwallet/newop/state.go b/libwallet/newop/state.go index 0d33d9d4..ac64028e 100644 --- a/libwallet/newop/state.go +++ b/libwallet/newop/state.go @@ -2,6 +2,7 @@ package newop import ( "fmt" + "log/slog" "path" "strings" "time" @@ -276,7 +277,6 @@ func loadFeeBumpFunctions() ([]*operation.FeeBumpFunction, error) { repository := db.NewFeeBumpRepository() feeBumpFunctions, err := repository.GetAll() - // TODO: Check if the data is invalidated, on that case we should refresh it if err != nil { return nil, err @@ -291,9 +291,12 @@ func (s *ResolveState) setContextWithTime(initialContext *InitialPaymentContext, var feeBumpFunctions []*operation.FeeBumpFunction if libwallet.DetermineBackendActivatedFeatureStatus(libwallet.BackendFeatureEffectiveFeesCalculation) { // Load fee bump functions from local DB - feeBumpFunctions, _ = loadFeeBumpFunctions() + var err error + feeBumpFunctions, err = loadFeeBumpFunctions() - // TODO: Handle error - tracking error and refresh fee bump functions + if err != nil { + slog.Error("error loading fee bump functions.", slog.Any("error", err)) + } } context := initialContext.newPaymentContext(feeBumpFunctions) diff --git a/libwallet/operation/fees.go b/libwallet/operation/fees.go index 2294b466..59c46dc7 100644 --- a/libwallet/operation/fees.go +++ b/libwallet/operation/fees.go @@ -2,7 +2,7 @@ package operation import ( "errors" - "fmt" + "log/slog" "math" ) @@ -43,8 +43,7 @@ func (f *feeCalculator) calculateFee(amountInSat int64, feeRateInSatsPerVByte fl var err error feeBumpAmount, err = f.calculateFeeBumpAmount(lastUnconfirmedUtxoUsedIndex, feeRateInSatsPerVByte) if err != nil { - // TODO: Add listener to track non-fatal error. - fmt.Printf("Non-fatal error calculating fee bump amount: %v\n", err.Error()) + slog.Error("error calculating fee bump amount.", slog.Any("error", err)) } } diff --git a/linters/findbugs/check-android.gradle b/linters/findbugs/check-android.gradle index 03b03889..0389e1f6 100644 --- a/linters/findbugs/check-android.gradle +++ b/linters/findbugs/check-android.gradle @@ -14,8 +14,8 @@ tasks.withType(SpotBugsTask) { classes = files("${project.buildDir}/intermediates/javac") reports { - xml.enabled = false - html.enabled = true + xml.required = false + html.required = true } } diff --git a/linters/findbugs/check.gradle b/linters/findbugs/check.gradle index 03777459..f62bb90f 100644 --- a/linters/findbugs/check.gradle +++ b/linters/findbugs/check.gradle @@ -12,8 +12,8 @@ task spotbugsMain(type: SpotBugsTask) { effort = 'max' reports { - xml.enabled = false - html.enabled = !xml.enabled + xml.required = false + html.required = !xml.required } } diff --git a/linters/pmd/check-android.gradle b/linters/pmd/check-android.gradle index 3963d575..fc2950f8 100644 --- a/linters/pmd/check-android.gradle +++ b/linters/pmd/check-android.gradle @@ -13,7 +13,7 @@ task pmd(type: Pmd) { source = fileTree('src/main/java') reports { - xml.enabled = false - html.enabled = !xml.enabled + xml.required = false + html.required = !xml.required } } diff --git a/linters/pmd/check.gradle b/linters/pmd/check.gradle index 21070e63..f7780cf5 100644 --- a/linters/pmd/check.gradle +++ b/linters/pmd/check.gradle @@ -13,7 +13,7 @@ tasks.withType(Pmd).configureEach { task -> check.dependsOn task reports { - xml.enabled = false - html.enabled = !xml.enabled + xml.required = false + html.required = !xml.required } } diff --git a/settings.gradle b/settings.gradle index 38bc5b58..32bf7c1f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,13 @@ +pluginManagement { + repositories { + mavenCentral() + maven { url "https://plugins.gradle.org/m2/" } + } + + plugins { + // Must match global_kotlin_version in top level build.gradle! + id 'org.jetbrains.kotlin.android' version "1.8.20" + } +} + include ':android:apollo', ':common', ':android:apolloui', ':android:libwallet'