From 8cf07491cf7c23d6a053fb4f139b50a43f9eb49c Mon Sep 17 00:00:00 2001 From: William Brawner Date: Tue, 18 Aug 2020 10:57:56 -0700 Subject: [PATCH] Re-implement ACRA This reverts commit 0e70364c6ea2f4802a5ed7e423bf79b80c9cf620. Signed-off-by: William Brawner --- app/.gitignore | 1 + app/build.gradle | 28 ++++--- app/proguard-rules.pro | 7 +- app/src/main/AndroidManifest.xml | 7 -- app/src/main/assets/Privacy Policy.md | 78 ++++++++++++++++--- .../simplemarkdown/MarkdownApplication.kt | 14 ++-- .../simplemarkdown/utility/ErrorHandler.kt | 77 ++++++++++++------ .../view/fragment/MainFragment.kt | 12 --- .../view/fragment/SettingsFragment.kt | 9 +++ app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/layout/fragment_settings.xml | 2 +- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/pref_general.xml | 2 +- build.gradle | 1 - sentry.properties.sample | 4 - 15 files changed, 166 insertions(+), 80 deletions(-) delete mode 100644 sentry.properties.sample diff --git a/app/.gitignore b/app/.gitignore index ebc6b31..659dd9f 100644 --- a/app/.gitignore +++ b/app/.gitignore @@ -1,3 +1,4 @@ /build *.apk /release +acra.properties diff --git a/app/build.gradle b/app/build.gradle index 8e6459a..5f505b4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,6 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'jacoco' -apply plugin: 'io.sentry.android.gradle' def keystoreProperties = new Properties() try { @@ -17,6 +16,17 @@ try { keystoreProperties['storePassword'] = "" } +def acraProperties = new Properties() +try { + def acraPropertiesFile = project.file("acra.properties") + acraProperties.load(new FileInputStream(acraPropertiesFile)) +} catch (FileNotFoundException ignored) { + logger.warn("Unable to load ACRA properties. Error reporting won't be available") + acraProperties['url'] = "" + acraProperties['user'] = "" + acraProperties['pass'] = "" +} + android { configurations.all { resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1' @@ -29,7 +39,6 @@ android { exclude 'META-INF/DEPENDENCIES' } compileSdkVersion 29 - buildToolsVersion '28.0.3' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -39,15 +48,15 @@ android { } defaultConfig { applicationId "com.wbrawner.simplemarkdown" - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 29 versionCode 27 versionName "0.8.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - manifestPlaceholders = [ - sentryDsn: "https://399270639b2e4b10a028a2be9192d1d3@sentry.brawner.dev/2" - ] buildConfigField "boolean", "ENABLE_CUSTOM_CSS", "false" + buildConfigField "String", "ACRA_URL", "\"${acraProperties['url']}\"" + buildConfigField "String", "ACRA_USER", "\"${acraProperties['user']}\"" + buildConfigField "String", "ACRA_PASS", "\"${acraProperties['pass']}\"" } signingConfigs { release { @@ -59,7 +68,6 @@ android { } buildTypes { debug { - applicationIdSuffix ".debug" testCoverageEnabled true } release { @@ -116,7 +124,6 @@ dependencies { implementation "androidx.core:core-ktx:1.3.0" implementation 'androidx.browser:browser:1.2.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'io.sentry:sentry-android:2.1.6' def coroutines_version = "1.3.4" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" @@ -124,6 +131,10 @@ dependencies { implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation 'eu.crydee:syllable-counter:4.0.2' + + def acraVersion = '5.7.0' + implementation "ch.acra:acra-http:$acraVersion" + implementation "ch.acra:acra-advanced-scheduler:$acraVersion" } android.productFlavors.each { flavor -> @@ -132,7 +143,6 @@ android.productFlavors.each { flavor -> apply plugin: 'com.google.gms.google-services' } } - repositories { mavenCentral() jcenter() diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 3cf3306..9965cce 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -24,7 +24,6 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile -### Crashlytics ### --keepattributes *Annotation* --keepattributes SourceFile,LineNumberTable -### End Crashlytics ### \ No newline at end of file +### ACRA ### +-keep class com.wbrawner.simplemarkdown.BuildConfig { *; } +### \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fd885fb..70f1d5d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,13 +52,6 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> - - - \ No newline at end of file diff --git a/app/src/main/assets/Privacy Policy.md b/app/src/main/assets/Privacy Policy.md index 578b86b..aa9fb79 100644 --- a/app/src/main/assets/Privacy Policy.md +++ b/app/src/main/assets/Privacy Policy.md @@ -1,10 +1,70 @@ +## Privacy Policy + First and foremost, Simple Markdown DOES NOT collect any personally identifiable information. The -internet access permission is requested primarily for retrieving images from the internet in -case you embed them in your markdown, but it also allows me to send automated error and crash -reports to myself whenever the app runs into an issue. These automated reports are powered by my own -self-hosted version of [Sentry] (https://sentry.io/), which is a free and open source error -reporting solution. These error reports are used exclusively for fixing problems that occur while -you're using the app. For more information on the kinds of data that may be sent in these automated -error reports, please see the [relevant documentation](https://docs.sentry.io/platforms/android/#context) -on Sentry's website. If you would like to opt-out of these error reports, please visit the in-app -settings page to disable the toggle for error reports. +internet access permission is requested primarily for retrieving images from the internet in case +you embed them in your markdown, but it also allows me to send automated error and crash reports +to myself whenever the app runs into an issue. These error reports are powered by [ACRA](https://github.com/ACRA/acra), which is +an open source error reporting solution, and are sent to a private server that only I have access +to, where it is stored for up to 90 days before being permanently deleted. These error reports are +used exclusively for fixing problems that occur while you're using the app, and contain only the +bare minimum information that I need to be able to resolve the issue. The information sent may +include: + +- the version of Android your device is running (e.g. 7.1) +- details about the version of SimpleMarkdown that you are using (Google Play version, Samsung Galaxy Apps version, FDroid version, version number, etc) +- any logs that SimpleMarkdown (but not other apps) might have created during execution +- the app identifier (com.wbrawner.simplemarkdown) +- your device's manufacturer (Samsung, Huawei, LG, etc) +- your device's model (Galaxy S8, Mate 10 pro, G7, etc) +- your device's configuration at app start and at the moment of the crash (the configuration tells me things like if you had the keyboard open, and if you were using the app in landscape or portrait mode. See the end of this privacy policy for a sample of that this might look like.) +- an estimation of the amount of available memory your device has (7353192448) +- an estimation of the amount of total memory your device has (55540875264) +- a stacktrace of the error (a stacktrace tells me which part of my code caused the error, what kind of error it was, and which parts of the code ran up to that error. See the end of this privacy policy for a sample of what this might look like.) +- any custom settings you have set within SimpleMarkdown (your default launch screen, default file directory, etc. Note that this does NOT include settings specific to your device, like whether or not you have WiFi enabled for example) +- details about which thread the crash occurred on (e.g. main, file handling, network) +- the time and date you started the app +- the time and date the crash occurred + +This information is the only information that I collect, and it's strictly used for finding and fixing issues in the code as quickly as possible. If you don't feel comfortable with this however, you are able to opt-out from the settings menu. Should you choose to opt-out of automated crash reports, I would very much appreciate it if you could contact me when you run into an issue either by email: [support@wbrawner.com](mailto:support@wbrawner.com) or by submitting an issue via the [GitHub page](https://github.com/wbrawner/SimpleMarkdown). + +### Sample data + +A sample configuration: + +``` +locale=fr_FR +hardKeyboardHidden=HARDKEYBOARDHIDDEN_YES +keyboard=KEYBOARD_NOKEYS +keyboardHidden=KEYBOARDHIDDEN_NO +fontScale=1.0 +mcc=208 +mnc=10 +navigation=NAVIGATION_TRACKBALL +navigationHidden=NAVIGATIONHIDDEN_NO +orientation=ORIENTATION_PORTRAIT +screenLayout=SCREENLAYOUT_SIZE_NORMAL+SCREENLAYOUT_LONG_YES +seq=117 +touchscreen=TOUCHSCREEN_FINGER +uiMode=UI_MODE_TYPE_NORMAL+UI_MODE_NIGHT_NO +userSetLocale=false +``` + +A sample stacktrace: + +``` +java.io.IOException: No such file or directory + at java.io.UnixFileSystem.createFileExclusively0(UnixFileSystem.java) + at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:280) + at java.io.File.createNewFile(File.java:948) + at com.wbrawner.simplemarkdown.model.MarkdownFile.save(MarkdownFile.java:152) + at com.wbrawner.simplemarkdown.presentation.MarkdownPresenterImpl.saveMarkdown(MarkdownPresenterImpl.java:120) + at com.wbrawner.simplemarkdown.presentation.MarkdownPresenterImpl.lambda$saveMarkdown$2$MarkdownPresenterImpl(MarkdownPresenterImpl.java:120) + at com.wbrawner.simplemarkdown.presentation.MarkdownPresenterImpl$$Lambda$2.run(MarkdownPresenterImpl.java) + at android.os.Handler.handleCallback(Handler.java:751) + at android.os.Handler.dispatchMessage(Handler.java:95) + at android.os.Looper.loop(Looper.java:154) + at android.app.ActivityThread.main(ActivityThread.java:6121) + at java.lang.reflect.Method.invoke(Method.java) + at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889) + at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779) +``` diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/MarkdownApplication.kt b/app/src/main/java/com/wbrawner/simplemarkdown/MarkdownApplication.kt index ab44f35..e4060b1 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/MarkdownApplication.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/MarkdownApplication.kt @@ -1,20 +1,17 @@ package com.wbrawner.simplemarkdown import android.app.Application +import android.content.Context import android.os.StrictMode -import androidx.preference.PreferenceManager +import com.wbrawner.simplemarkdown.utility.AcraErrorHandler import com.wbrawner.simplemarkdown.utility.ErrorHandler -import com.wbrawner.simplemarkdown.utility.SentryErrorHandler class MarkdownApplication : Application() { val errorHandler: ErrorHandler by lazy { - SentryErrorHandler() + AcraErrorHandler() } override fun onCreate() { - val enableErrorReports = PreferenceManager.getDefaultSharedPreferences(this) - .getBoolean(getString(R.string.error_reports_enabled), true) - errorHandler.init(this, enableErrorReports) if (BuildConfig.DEBUG) { StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() .detectAll() @@ -27,4 +24,9 @@ class MarkdownApplication : Application() { } super.onCreate() } + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + errorHandler.init(this) + } } diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/utility/ErrorHandler.kt b/app/src/main/java/com/wbrawner/simplemarkdown/utility/ErrorHandler.kt index ad23c6e..c91a5d1 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/utility/ErrorHandler.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/utility/ErrorHandler.kt @@ -1,43 +1,72 @@ package com.wbrawner.simplemarkdown.utility -import android.content.Context +import android.app.Application +import android.app.job.JobInfo import android.util.Log import com.wbrawner.simplemarkdown.BuildConfig -import io.sentry.android.core.SentryAndroid -import io.sentry.core.Sentry -import java.util.concurrent.atomic.AtomicBoolean +import org.acra.ACRA +import org.acra.ReportField +import org.acra.config.CoreConfigurationBuilder +import org.acra.config.HttpSenderConfigurationBuilder +import org.acra.config.SchedulerConfigurationBuilder +import org.acra.data.StringFormat +import org.acra.sender.HttpSender interface ErrorHandler { - fun init(context: Context, enable: Boolean) - fun enable(enable: Boolean) + fun init(application: Application) fun reportException(t: Throwable, message: String? = null) } -class SentryErrorHandler : ErrorHandler { - private lateinit var enabled: AtomicBoolean +class AcraErrorHandler : ErrorHandler { - override fun init(context: Context, enable: Boolean) { - enabled = AtomicBoolean(enable) - SentryAndroid.init(context) { options -> - options.setBeforeSend { event, _ -> - if (enabled.get()) { - event - } else { - null - } - } + override fun init(application: Application) { + if (BuildConfig.ACRA_URL.isBlank() + || BuildConfig.ACRA_USER.isBlank() + || BuildConfig.ACRA_PASS.isBlank()) { + return } - } - - override fun enable(enable: Boolean) { - enabled.set(enable) + val builder = CoreConfigurationBuilder(application) + .setBuildConfigClass(BuildConfig::class.java) + .setReportFormat(StringFormat.JSON) + .setReportContent( + ReportField.ANDROID_VERSION, + ReportField.APP_VERSION_CODE, + ReportField.APP_VERSION_NAME, + ReportField.APPLICATION_LOG, + ReportField.AVAILABLE_MEM_SIZE, + ReportField.BRAND, + ReportField.BUILD_CONFIG, + ReportField.CRASH_CONFIGURATION, + ReportField.CUSTOM_DATA, // Not currently used, but might be useful in the future + ReportField.INITIAL_CONFIGURATION, + ReportField.PACKAGE_NAME, + ReportField.PHONE_MODEL, + ReportField.SHARED_PREFERENCES, + ReportField.STACK_TRACE, + ReportField.STACK_TRACE_HASH, + ReportField.THREAD_DETAILS, + ReportField.TOTAL_MEM_SIZE, + ReportField.USER_APP_START_DATE, + ReportField.USER_CRASH_DATE + ) + builder.getPluginConfigurationBuilder(HttpSenderConfigurationBuilder::class.java) + .setUri(BuildConfig.ACRA_URL) + .setHttpMethod(HttpSender.Method.POST) + .setBasicAuthLogin(BuildConfig.ACRA_USER) + .setBasicAuthPassword(BuildConfig.ACRA_PASS) + .setEnabled(true) + builder.getPluginConfigurationBuilder(SchedulerConfigurationBuilder::class.java) + .setRequiresNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) + .setRequiresBatteryNotLow(true) + .setEnabled(true) + ACRA.init(application, builder) } override fun reportException(t: Throwable, message: String?) { @Suppress("ConstantConditionIf") if (BuildConfig.DEBUG) { - Log.e("SentryErrorHandler", "Caught exception: $message", t) + Log.e("AcraErrorHandler", "Caught exception: $message", t) } - Sentry.captureException(t) + ACRA.getErrorReporter().handleException(t) } } \ No newline at end of file diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt index d06ac70..7e02ea0 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/MainFragment.kt @@ -24,7 +24,6 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager -import com.wbrawner.simplemarkdown.MarkdownApplication import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel @@ -116,17 +115,6 @@ class MainFragment : Fragment(), ActivityCompat.OnRequestPermissionsResultCallba }) } - override fun onStart() { - super.onStart() - launch { - withContext(Dispatchers.IO) { - val enableErrorReports = PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.error_reports_enabled), true) - (requireActivity().application as MarkdownApplication).errorHandler.enable(enableErrorReports) - } - } - } - override fun onPause() { super.onPause() launch { diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsFragment.kt b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsFragment.kt index cb7574e..7fbdae5 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsFragment.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/view/fragment/SettingsFragment.kt @@ -5,6 +5,7 @@ import android.os.Build import android.os.Bundle import androidx.appcompat.app.AppCompatDelegate import androidx.preference.ListPreference +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.wbrawner.simplemarkdown.BuildConfig @@ -20,6 +21,14 @@ class SettingsFragment CoroutineScope { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_general) + if (BuildConfig.DEBUG) { + preferenceScreen.addPreference(Preference(preferenceScreen.context).apply { + title = "Force crash" + onPreferenceClickListener = Preference.OnPreferenceClickListener { + throw RuntimeException("Forced crash from settings") + } + }) + } } override val coroutineContext: CoroutineContext = Dispatchers.Main diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 1a47a1a..51f7c60 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,5 +1,5 @@ - - Lock Swiping Select Privacy - errors.enable + acra.enable Enable automated error reports Error reports will not be sent Error reports will be sent diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 98aa6b3..be5bde3 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -19,7 +19,7 @@ android:title="@string/title_dark_mode" /> diff --git a/build.gradle b/build.gradle index d9a1ac5..1da8f69 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.google.gms:google-services:4.3.3' - classpath 'io.sentry:sentry-android-gradle-plugin:1.7.34' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/sentry.properties.sample b/sentry.properties.sample deleted file mode 100644 index 243594f..0000000 --- a/sentry.properties.sample +++ /dev/null @@ -1,4 +0,0 @@ -defaults.url=https://sentry.brawner.dev/ -defaults.project=simplemarkdown -defaults.org=wbrawner -auth.token=