diff --git a/app/build.gradle b/app/build.gradle index 1c44b4a..2293a2d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,8 +5,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'com.google.gms.google-services' -apply plugin: 'io.fabric' -apply plugin: 'com.google.firebase.firebase-perf' +apply plugin: 'com.google.firebase.crashlytics' //apply plugin: 'com.github.ben-manes.versions' archivesBaseName = 'zcash-android-wallet' @@ -102,10 +101,6 @@ android { } } -crashlytics { - enableNdk true -} - dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':qrecycler') @@ -161,9 +156,6 @@ dependencies { // Analytics (for dogfooding/crash-reporting/feedback only on internal team builds) implementation Deps.Analytics.CRASHLYTICS - implementation Deps.Analytics.CRASHLYTICS_NDK - implementation Deps.Analytics.FIREBASE - implementation Deps.Analytics.FIREBASE_PERF implementation Deps.Analytics.MIXPANEL // Misc. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7b5fa4f..099b1b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,6 +33,8 @@ + + diff --git a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt index fc3ab61..44ee9f9 100644 --- a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt +++ b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt @@ -37,7 +37,6 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider { override fun onCreate() { Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler())) - Twig.plant(TroubleshootingTwig()) creationTime = System.currentTimeMillis() instance = this // Setup handler for uncaught exceptions. diff --git a/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt index 92870ce..948eabd 100644 --- a/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt @@ -2,9 +2,14 @@ package cash.z.ecc.android.di.module import android.content.ClipboardManager import android.content.Context +import android.content.SharedPreferences import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.di.component.MainActivitySubcomponent import cash.z.ecc.android.feedback.* +import cash.z.ecc.android.sdk.ext.SilentTwig +import cash.z.ecc.android.sdk.ext.TroubleshootingTwig +import cash.z.ecc.android.sdk.ext.Twig +import com.google.firebase.crashlytics.FirebaseCrashlytics import dagger.Module import dagger.Provides import dagger.multibindings.IntoSet @@ -27,6 +32,11 @@ class AppModule { // Feedback // + @Provides + @Singleton + fun providePreferences(context: Context): SharedPreferences + = context.getSharedPreferences("Application", Context.MODE_PRIVATE) + @Provides @Singleton fun provideFeedback(): Feedback = Feedback() @@ -35,8 +45,16 @@ class AppModule { @Singleton fun provideFeedbackCoordinator( feedback: Feedback, + preferences: SharedPreferences, defaultObservers: Set<@JvmSuppressWildcards FeedbackCoordinator.FeedbackObserver> - ): FeedbackCoordinator = FeedbackCoordinator(feedback, defaultObservers) + ): FeedbackCoordinator { + return preferences.getBoolean(FeedbackCoordinator.ENABLED, false).let { isEnabled -> + // observe nothing unless feedback is enabled + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(isEnabled) + Twig.plant(if (isEnabled) TroubleshootingTwig() else SilentTwig()) + FeedbackCoordinator(feedback, if (isEnabled) defaultObservers else setOf()) + } + } // diff --git a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackCrashlytics.kt b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackCrashlytics.kt index d3e5d30..ba146fb 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/FeedbackCrashlytics.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/FeedbackCrashlytics.kt @@ -1,6 +1,6 @@ package cash.z.ecc.android.feedback -import com.crashlytics.android.Crashlytics +import com.google.firebase.crashlytics.FirebaseCrashlytics class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver { /** @@ -18,7 +18,7 @@ class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver { ) else -> null } - exception?.let { Crashlytics.logException(it) } + exception?.let { FirebaseCrashlytics.getInstance().recordException(it) } } private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) : diff --git a/app/src/main/java/cash/z/ecc/android/feedback/Report.kt b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt index b6899b3..f43f7a9 100644 --- a/app/src/main/java/cash/z/ecc/android/feedback/Report.kt +++ b/app/src/main/java/cash/z/ecc/android/feedback/Report.kt @@ -63,6 +63,7 @@ object Report { val errorHeight: Int by propertyMap val rewindHeight: Int by propertyMap } + class TxUpdateFailed(t: Throwable) : Feedback.AppError("txupdate", t, false) } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/detail/TransactionViewHolder.kt b/app/src/main/java/cash/z/ecc/android/ui/detail/TransactionViewHolder.kt index 2389a0c..d0adffe 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/detail/TransactionViewHolder.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/detail/TransactionViewHolder.kt @@ -29,73 +29,75 @@ class TransactionViewHolder(itemView: View) : Recycler private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault()) private val addressRegex = """zs\d\w{65,}""".toRegex() - fun bindTo(transaction: T?) = (itemView.context as MainActivity).lifecycleScope.launch { - // update view - var lineOne: String = "" - var lineTwo: String = "" - var amountZec: String = "" - var amountDisplay: String = "" - var amountColor: Int = R.color.text_light_dimmed - var lineOneColor: Int = R.color.text_light - var lineTwoColor: Int = R.color.text_light_dimmed - var indicatorBackground: Int = R.drawable.background_indicator_unknown + fun bindTo(transaction: T?) { + (itemView.context as MainActivity).lifecycleScope.launch { + // update view + var lineOne: String = "" + var lineTwo: String = "" + var amountZec: String = "" + var amountDisplay: String = "" + var amountColor: Int = R.color.text_light_dimmed + var lineOneColor: Int = R.color.text_light + var lineTwoColor: Int = R.color.text_light_dimmed + var indicatorBackground: Int = R.drawable.background_indicator_unknown - transaction?.apply { - itemView.setOnClickListener { - onTransactionClicked(this) - } - itemView.setOnLongClickListener { - onTransactionLongPressed(this) - true - } - amountZec = value.convertZatoshiToZecString() - // TODO: these might be good extension functions - val timestamp = formatter.format(blockTimeInSeconds * 1000L) - val isMined = blockTimeInSeconds != 0L - when { - !toAddress.isNullOrEmpty() -> { - lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}" - lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation" - amountDisplay = "- $amountZec" - if (isMined) { - amountColor = R.color.zcashRed - indicatorBackground = R.drawable.background_indicator_outbound - } else { - lineOneColor = R.color.text_light_dimmed - lineTwoColor = R.color.text_light + transaction?.apply { + itemView.setOnClickListener { + onTransactionClicked(this) + } + itemView.setOnLongClickListener { + onTransactionLongPressed(this) + true + } + amountZec = value.convertZatoshiToZecString() + // TODO: these might be good extension functions + val timestamp = formatter.format(blockTimeInSeconds * 1000L) + val isMined = blockTimeInSeconds != 0L + when { + !toAddress.isNullOrEmpty() -> { + lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}" + lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation" + amountDisplay = "- $amountZec" + if (isMined) { + amountColor = R.color.zcashRed + indicatorBackground = R.drawable.background_indicator_outbound + } else { + lineOneColor = R.color.text_light_dimmed + lineTwoColor = R.color.text_light + } + } + toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> { + lineOne = getSender(transaction) + lineTwo = "Received $timestamp" + amountDisplay = "+ $amountZec" + amountColor = R.color.zcashGreen + indicatorBackground = R.drawable.background_indicator_inbound + } + else -> { + lineOne = "Unknown" + lineTwo = "Unknown" + amountDisplay = "$amountZec" + amountColor = R.color.text_light } } - toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> { - lineOne = getSender(transaction) - lineTwo = "Received $timestamp" - amountDisplay = "+ $amountZec" - amountColor = R.color.zcashGreen - indicatorBackground = R.drawable.background_indicator_inbound - } - else -> { - lineOne = "Unknown" - lineTwo = "Unknown" - amountDisplay = "$amountZec" - amountColor = R.color.text_light + // sanitize amount + if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001" + else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal + amountDisplay = "tap to view" } } - // sanitize amount - if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001" - else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal - amountDisplay = "tap to view" - } + + + topText.text = lineOne + bottomText.text = lineTwo + amountText.text = amountDisplay + amountText.setTextColor(amountColor.toAppColor()) + topText.setTextColor(lineOneColor.toAppColor()) + bottomText.setTextColor(lineTwoColor.toAppColor()) + val context = itemView.context + indicator.background = context.resources.getDrawable(indicatorBackground) + shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded()) } - - - topText.text = lineOne - bottomText.text = lineTwo - amountText.text = amountDisplay - amountText.setTextColor(amountColor.toAppColor()) - topText.setTextColor(lineOneColor.toAppColor()) - bottomText.setTextColor(lineTwoColor.toAppColor()) - val context = itemView.context - indicator.background = context.resources.getDrawable(indicatorBackground) - shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded()) } private suspend fun getSender(transaction: ConfirmedTransaction): String { diff --git a/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt index 67d22c6..07f152f 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/scan/ScanFragment.kt @@ -21,8 +21,8 @@ import cash.z.ecc.android.feedback.Report.Tap.SCAN_RECEIVE import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.send.SendViewModel -import com.crashlytics.android.Crashlytics import com.google.common.util.concurrent.ListenableFuture +import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.coroutines.launch import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -102,7 +102,7 @@ class ScanFragment : BaseFragment() { preview.setSurfaceProvider(binding.preview.createSurfaceProvider()) } catch (t: Throwable) { // TODO: consider bubbling this up to the user - Crashlytics.logException(t) + mainActivity?.feedback?.report(t) twig("Error while opening the camera: $t") } diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt index 814c6b1..c4c11ea 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt @@ -16,7 +16,6 @@ import cash.z.ecc.android.sdk.db.entity.* import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.twig -import com.crashlytics.android.Crashlytics import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn @@ -112,8 +111,8 @@ class SendFinalFragment : BaseFragment() { } catch(t: Throwable) { val message = "ERROR: error while handling pending transaction update! $t" twig(message) - Crashlytics.log(message) - Crashlytics.logException(t) + mainActivity?.feedback?.report(Report.Error.NonFatal.TxUpdateFailed(t)) + mainActivity?.feedback?.report(t) } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt index 9756f4b..5a01a9f 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt @@ -21,7 +21,6 @@ import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.validate.AddressType -import com.crashlytics.android.Crashlytics import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -144,7 +143,7 @@ class SendViewModel @Inject constructor() : ViewModel() { report(metricId) } } catch (t: Throwable) { - Crashlytics.logException(RuntimeException("Error while updating Metrics", t)) + feedback.report(t) } } diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt index 9d5952b..7990411 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt @@ -10,11 +10,9 @@ import android.widget.TextView import android.widget.Toast import androidx.activity.addCallback import androidx.lifecycle.lifecycleScope -import cash.z.ecc.android.R import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.databinding.FragmentBackupBinding import cash.z.ecc.android.di.viewmodel.activityViewModel -import cash.z.ecc.android.di.viewmodel.viewModel import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE diff --git a/build.gradle b/build.gradle index 7584f5d..a335c0f 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}" classpath 'io.fabric.tools:gradle:1.31.2' - classpath 'com.google.firebase:perf-plugin:1.3.1' + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' } } diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt index 0642076..d091f1b 100644 --- a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -61,10 +61,7 @@ object Deps { val STUB = "io.grpc:grpc-stub:$version" } object Analytics { // for dogfooding/crash-reporting/feedback only on internal team builds - val CRASHLYTICS = "com.crashlytics.sdk.android:crashlytics:2.10.1" - val CRASHLYTICS_NDK = "com.crashlytics.sdk.android:crashlytics-ndk:2.1.1" - val FIREBASE = "com.google.firebase:firebase-analytics:17.4.3" - val FIREBASE_PERF = "com.google.firebase:firebase-perf:19.0.7" + val CRASHLYTICS = "com.google.firebase:firebase-crashlytics:17.0.1" val MIXPANEL = "com.mixpanel.android:mixpanel-android:5.6.3" } object JavaX { diff --git a/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt b/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt index 133f1f1..36f1359 100644 --- a/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt +++ b/feedback/src/main/java/cash/z/ecc/android/feedback/FeedbackCoordinator.kt @@ -125,6 +125,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set