Explicitly disable all feedback, dev info and crash reporting.

Addresses https://github.com/zcash/zcash-android-wallet/issues/143 by placing everything behind a user setting that can be enabled in the future by users who want to continue helping us improve the user experience. For the most part, this will just be turned on for internal company releases in order to continue learning and improving the app.
This commit is contained in:
Kevin Gorham 2020-06-10 10:01:58 -04:00
parent 340fb8c993
commit de69567812
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
14 changed files with 96 additions and 88 deletions

View File

@ -5,8 +5,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric' apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.firebase.firebase-perf'
//apply plugin: 'com.github.ben-manes.versions' //apply plugin: 'com.github.ben-manes.versions'
archivesBaseName = 'zcash-android-wallet' archivesBaseName = 'zcash-android-wallet'
@ -102,10 +101,6 @@ android {
} }
} }
crashlytics {
enableNdk true
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':qrecycler') implementation project(':qrecycler')
@ -161,9 +156,6 @@ dependencies {
// Analytics (for dogfooding/crash-reporting/feedback only on internal team builds) // Analytics (for dogfooding/crash-reporting/feedback only on internal team builds)
implementation Deps.Analytics.CRASHLYTICS implementation Deps.Analytics.CRASHLYTICS
implementation Deps.Analytics.CRASHLYTICS_NDK
implementation Deps.Analytics.FIREBASE
implementation Deps.Analytics.FIREBASE_PERF
implementation Deps.Analytics.MIXPANEL implementation Deps.Analytics.MIXPANEL
// Misc. // Misc.

View File

@ -33,6 +33,8 @@
<!-- Firebase options --> <!-- Firebase options -->
<meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" /> <meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" />
<!-- All reporting is opt-in, only -->
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<!-- Mixpanel options --> <!-- Mixpanel options -->
<meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" /> <meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" />

View File

@ -37,7 +37,6 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider {
override fun onCreate() { override fun onCreate() {
Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler())) Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler()))
Twig.plant(TroubleshootingTwig())
creationTime = System.currentTimeMillis() creationTime = System.currentTimeMillis()
instance = this instance = this
// Setup handler for uncaught exceptions. // Setup handler for uncaught exceptions.

View File

@ -2,9 +2,14 @@ package cash.z.ecc.android.di.module
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.di.component.MainActivitySubcomponent import cash.z.ecc.android.di.component.MainActivitySubcomponent
import cash.z.ecc.android.feedback.* 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.Module
import dagger.Provides import dagger.Provides
import dagger.multibindings.IntoSet import dagger.multibindings.IntoSet
@ -27,6 +32,11 @@ class AppModule {
// Feedback // Feedback
// //
@Provides
@Singleton
fun providePreferences(context: Context): SharedPreferences
= context.getSharedPreferences("Application", Context.MODE_PRIVATE)
@Provides @Provides
@Singleton @Singleton
fun provideFeedback(): Feedback = Feedback() fun provideFeedback(): Feedback = Feedback()
@ -35,8 +45,16 @@ class AppModule {
@Singleton @Singleton
fun provideFeedbackCoordinator( fun provideFeedbackCoordinator(
feedback: Feedback, feedback: Feedback,
preferences: SharedPreferences,
defaultObservers: Set<@JvmSuppressWildcards FeedbackCoordinator.FeedbackObserver> 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())
}
}
// //

View File

@ -1,6 +1,6 @@
package cash.z.ecc.android.feedback package cash.z.ecc.android.feedback
import com.crashlytics.android.Crashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver { class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver {
/** /**
@ -18,7 +18,7 @@ class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver {
) )
else -> null else -> null
} }
exception?.let { Crashlytics.logException(it) } exception?.let { FirebaseCrashlytics.getInstance().recordException(it) }
} }
private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) : private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) :

View File

@ -63,6 +63,7 @@ object Report {
val errorHeight: Int by propertyMap val errorHeight: Int by propertyMap
val rewindHeight: Int by propertyMap val rewindHeight: Int by propertyMap
} }
class TxUpdateFailed(t: Throwable) : Feedback.AppError("txupdate", t, false)
} }
} }

View File

@ -29,73 +29,75 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault()) private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
private val addressRegex = """zs\d\w{65,}""".toRegex() private val addressRegex = """zs\d\w{65,}""".toRegex()
fun bindTo(transaction: T?) = (itemView.context as MainActivity).lifecycleScope.launch { fun bindTo(transaction: T?) {
// update view (itemView.context as MainActivity).lifecycleScope.launch {
var lineOne: String = "" // update view
var lineTwo: String = "" var lineOne: String = ""
var amountZec: String = "" var lineTwo: String = ""
var amountDisplay: String = "" var amountZec: String = ""
var amountColor: Int = R.color.text_light_dimmed var amountDisplay: String = ""
var lineOneColor: Int = R.color.text_light var amountColor: Int = R.color.text_light_dimmed
var lineTwoColor: Int = R.color.text_light_dimmed var lineOneColor: Int = R.color.text_light
var indicatorBackground: Int = R.drawable.background_indicator_unknown var lineTwoColor: Int = R.color.text_light_dimmed
var indicatorBackground: Int = R.drawable.background_indicator_unknown
transaction?.apply { transaction?.apply {
itemView.setOnClickListener { itemView.setOnClickListener {
onTransactionClicked(this) onTransactionClicked(this)
} }
itemView.setOnLongClickListener { itemView.setOnLongClickListener {
onTransactionLongPressed(this) onTransactionLongPressed(this)
true true
} }
amountZec = value.convertZatoshiToZecString() amountZec = value.convertZatoshiToZecString()
// TODO: these might be good extension functions // TODO: these might be good extension functions
val timestamp = formatter.format(blockTimeInSeconds * 1000L) val timestamp = formatter.format(blockTimeInSeconds * 1000L)
val isMined = blockTimeInSeconds != 0L val isMined = blockTimeInSeconds != 0L
when { when {
!toAddress.isNullOrEmpty() -> { !toAddress.isNullOrEmpty() -> {
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}" lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation" lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
amountDisplay = "- $amountZec" amountDisplay = "- $amountZec"
if (isMined) { if (isMined) {
amountColor = R.color.zcashRed amountColor = R.color.zcashRed
indicatorBackground = R.drawable.background_indicator_outbound indicatorBackground = R.drawable.background_indicator_outbound
} else { } else {
lineOneColor = R.color.text_light_dimmed lineOneColor = R.color.text_light_dimmed
lineTwoColor = R.color.text_light 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 -> { // sanitize amount
lineOne = getSender(transaction) if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001"
lineTwo = "Received $timestamp" else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal
amountDisplay = "+ $amountZec" amountDisplay = "tap to view"
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 topText.text = lineOne
amountDisplay = "tap to view" 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 { private suspend fun getSender(transaction: ConfirmedTransaction): String {

View File

@ -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.sdk.ext.twig
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.send.SendViewModel import cash.z.ecc.android.ui.send.SendViewModel
import com.crashlytics.android.Crashlytics
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -102,7 +102,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
preview.setSurfaceProvider(binding.preview.createSurfaceProvider()) preview.setSurfaceProvider(binding.preview.createSurfaceProvider())
} catch (t: Throwable) { } catch (t: Throwable) {
// TODO: consider bubbling this up to the user // TODO: consider bubbling this up to the user
Crashlytics.logException(t) mainActivity?.feedback?.report(t)
twig("Error while opening the camera: $t") twig("Error while opening the camera: $t")
} }

View File

@ -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.convertZatoshiToZecString
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -112,8 +111,8 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
} catch(t: Throwable) { } catch(t: Throwable) {
val message = "ERROR: error while handling pending transaction update! $t" val message = "ERROR: error while handling pending transaction update! $t"
twig(message) twig(message)
Crashlytics.log(message) mainActivity?.feedback?.report(Report.Error.NonFatal.TxUpdateFailed(t))
Crashlytics.logException(t) mainActivity?.feedback?.report(t)
} }
} }

View File

@ -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.convertZatoshiToZecString
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.validate.AddressType import cash.z.ecc.android.sdk.validate.AddressType
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -144,7 +143,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
report(metricId) report(metricId)
} }
} catch (t: Throwable) { } catch (t: Throwable) {
Crashlytics.logException(RuntimeException("Error while updating Metrics", t)) feedback.report(t)
} }
} }

View File

@ -10,11 +10,9 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentBackupBinding import cash.z.ecc.android.databinding.FragmentBackupBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel 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
import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED
import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE

View File

@ -13,7 +13,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
classpath 'io.fabric.tools:gradle:1.31.2' 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'
} }
} }

View File

@ -61,10 +61,7 @@ object Deps {
val STUB = "io.grpc:grpc-stub:$version" val STUB = "io.grpc:grpc-stub:$version"
} }
object Analytics { // for dogfooding/crash-reporting/feedback only on internal team builds object Analytics { // for dogfooding/crash-reporting/feedback only on internal team builds
val CRASHLYTICS = "com.crashlytics.sdk.android:crashlytics:2.10.1" val CRASHLYTICS = "com.google.firebase:firebase-crashlytics:17.0.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 MIXPANEL = "com.mixpanel.android:mixpanel-android:5.6.3" val MIXPANEL = "com.mixpanel.android:mixpanel-android:5.6.3"
} }
object JavaX { object JavaX {

View File

@ -125,6 +125,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
} }
companion object { companion object {
const val ENABLED = "setting.feedbackcoordinater.enabled"
private val mutex: Mutex = Mutex() private val mutex: Mutex = Mutex()
} }
} }