Re-implement ACRA

This reverts commit 0e70364c6e.

Signed-off-by: William Brawner <me@wbrawner.com>
This commit is contained in:
William Brawner 2020-08-18 10:57:56 -07:00
parent 3b984e4b73
commit 8cf07491cf
15 changed files with 166 additions and 80 deletions

1
app/.gitignore vendored
View file

@ -1,3 +1,4 @@
/build /build
*.apk *.apk
/release /release
acra.properties

View file

@ -3,7 +3,6 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'jacoco' apply plugin: 'jacoco'
apply plugin: 'io.sentry.android.gradle'
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
try { try {
@ -17,6 +16,17 @@ try {
keystoreProperties['storePassword'] = "" 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 { android {
configurations.all { configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1' resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.1'
@ -29,7 +39,6 @@ android {
exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/DEPENDENCIES'
} }
compileSdkVersion 29 compileSdkVersion 29
buildToolsVersion '28.0.3'
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
@ -39,15 +48,15 @@ android {
} }
defaultConfig { defaultConfig {
applicationId "com.wbrawner.simplemarkdown" applicationId "com.wbrawner.simplemarkdown"
minSdkVersion 21 minSdkVersion 23
targetSdkVersion 29 targetSdkVersion 29
versionCode 27 versionCode 27
versionName "0.8.5" versionName "0.8.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
manifestPlaceholders = [
sentryDsn: "https://399270639b2e4b10a028a2be9192d1d3@sentry.brawner.dev/2"
]
buildConfigField "boolean", "ENABLE_CUSTOM_CSS", "false" 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 { signingConfigs {
release { release {
@ -59,7 +68,6 @@ android {
} }
buildTypes { buildTypes {
debug { debug {
applicationIdSuffix ".debug"
testCoverageEnabled true testCoverageEnabled true
} }
release { release {
@ -116,7 +124,6 @@ dependencies {
implementation "androidx.core:core-ktx:1.3.0" implementation "androidx.core:core-ktx:1.3.0"
implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.browser:browser:1.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'io.sentry:sentry-android:2.1.6'
def coroutines_version = "1.3.4" def coroutines_version = "1.3.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
@ -124,6 +131,10 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
implementation 'eu.crydee:syllable-counter:4.0.2' 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 -> android.productFlavors.each { flavor ->
@ -132,7 +143,6 @@ android.productFlavors.each { flavor ->
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
} }
} }
repositories { repositories {
mavenCentral() mavenCentral()
jcenter() jcenter()

View file

@ -24,7 +24,6 @@
# hide the original source file name. # hide the original source file name.
#-renamesourcefileattribute SourceFile #-renamesourcefileattribute SourceFile
### Crashlytics ### ### ACRA ###
-keepattributes *Annotation* -keep class com.wbrawner.simplemarkdown.BuildConfig { *; }
-keepattributes SourceFile,LineNumberTable ###
### End Crashlytics ###

View file

@ -52,13 +52,6 @@
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" /> android:resource="@xml/file_paths" />
</provider> </provider>
<meta-data
android:name="io.sentry.dsn"
android:value="${sentryDsn}" />
<meta-data
android:name="io.sentry.auto-init"
android:value="false" />
</application> </application>
</manifest> </manifest>

View file

@ -1,10 +1,70 @@
## Privacy Policy
First and foremost, Simple Markdown DOES NOT collect any personally identifiable information. The 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 internet access permission is requested primarily for retrieving images from the internet in case
case you embed them in your markdown, but it also allows me to send automated error and crash you embed them in your markdown, but it also allows me to send automated error and crash reports
reports to myself whenever the app runs into an issue. These automated reports are powered by my own to myself whenever the app runs into an issue. These error reports are powered by [ACRA](https://github.com/ACRA/acra), which is
self-hosted version of [Sentry] (https://sentry.io/), which is a free and open source error an open source error reporting solution, and are sent to a private server that only I have access
reporting solution. These error reports are used exclusively for fixing problems that occur while to, where it is stored for up to 90 days before being permanently deleted. These error reports are
you're using the app. For more information on the kinds of data that may be sent in these automated used exclusively for fixing problems that occur while you're using the app, and contain only the
error reports, please see the [relevant documentation](https://docs.sentry.io/platforms/android/#context) bare minimum information that I need to be able to resolve the issue. The information sent may
on Sentry's website. If you would like to opt-out of these error reports, please visit the in-app include:
settings page to disable the toggle for error reports.
- 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)
```

View file

@ -1,20 +1,17 @@
package com.wbrawner.simplemarkdown package com.wbrawner.simplemarkdown
import android.app.Application import android.app.Application
import android.content.Context
import android.os.StrictMode 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.ErrorHandler
import com.wbrawner.simplemarkdown.utility.SentryErrorHandler
class MarkdownApplication : Application() { class MarkdownApplication : Application() {
val errorHandler: ErrorHandler by lazy { val errorHandler: ErrorHandler by lazy {
SentryErrorHandler() AcraErrorHandler()
} }
override fun onCreate() { override fun onCreate() {
val enableErrorReports = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.error_reports_enabled), true)
errorHandler.init(this, enableErrorReports)
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder() StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
.detectAll() .detectAll()
@ -27,4 +24,9 @@ class MarkdownApplication : Application() {
} }
super.onCreate() super.onCreate()
} }
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
errorHandler.init(this)
}
} }

View file

@ -1,43 +1,72 @@
package com.wbrawner.simplemarkdown.utility package com.wbrawner.simplemarkdown.utility
import android.content.Context import android.app.Application
import android.app.job.JobInfo
import android.util.Log import android.util.Log
import com.wbrawner.simplemarkdown.BuildConfig import com.wbrawner.simplemarkdown.BuildConfig
import io.sentry.android.core.SentryAndroid import org.acra.ACRA
import io.sentry.core.Sentry import org.acra.ReportField
import java.util.concurrent.atomic.AtomicBoolean 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 { interface ErrorHandler {
fun init(context: Context, enable: Boolean) fun init(application: Application)
fun enable(enable: Boolean)
fun reportException(t: Throwable, message: String? = null) fun reportException(t: Throwable, message: String? = null)
} }
class SentryErrorHandler : ErrorHandler { class AcraErrorHandler : ErrorHandler {
private lateinit var enabled: AtomicBoolean
override fun init(context: Context, enable: Boolean) { override fun init(application: Application) {
enabled = AtomicBoolean(enable) if (BuildConfig.ACRA_URL.isBlank()
SentryAndroid.init(context) { options -> || BuildConfig.ACRA_USER.isBlank()
options.setBeforeSend { event, _ -> || BuildConfig.ACRA_PASS.isBlank()) {
if (enabled.get()) { return
event
} else {
null
}
}
} }
} val builder = CoreConfigurationBuilder(application)
.setBuildConfigClass(BuildConfig::class.java)
override fun enable(enable: Boolean) { .setReportFormat(StringFormat.JSON)
enabled.set(enable) .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?) { override fun reportException(t: Throwable, message: String?) {
@Suppress("ConstantConditionIf") @Suppress("ConstantConditionIf")
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Log.e("SentryErrorHandler", "Caught exception: $message", t) Log.e("AcraErrorHandler", "Caught exception: $message", t)
} }
Sentry.captureException(t) ACRA.getErrorReporter().handleException(t)
} }
} }

View file

@ -24,7 +24,6 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.onNavDestinationSelected
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.MarkdownApplication
import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.R
import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter import com.wbrawner.simplemarkdown.view.adapter.EditPagerAdapter
import com.wbrawner.simplemarkdown.viewmodel.MarkdownViewModel 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() { override fun onPause() {
super.onPause() super.onPause()
launch { launch {

View file

@ -5,6 +5,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.wbrawner.simplemarkdown.BuildConfig import com.wbrawner.simplemarkdown.BuildConfig
@ -20,6 +21,14 @@ class SettingsFragment
CoroutineScope { CoroutineScope {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_general) 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 override val coroutineContext: CoroutineContext = Dispatchers.Main

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android" <fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content" android:id="@+id/content"
android:name="androidx.navigation.fragment.NavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment"

View file

@ -16,7 +16,7 @@
android:background="@color/colorBackground" /> android:background="@color/colorBackground" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView <fragment
android:id="@+id/fragment_settings" android:id="@+id/fragment_settings"
android:name="com.wbrawner.simplemarkdown.view.fragment.SettingsFragment" android:name="com.wbrawner.simplemarkdown.view.fragment.SettingsFragment"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -34,7 +34,7 @@
<string name="action_lock_swipe">Lock Swiping</string> <string name="action_lock_swipe">Lock Swiping</string>
<string name="action_select">Select</string> <string name="action_select">Select</string>
<string name="action_privacy">Privacy</string> <string name="action_privacy">Privacy</string>
<string name="error_reports_enabled">errors.enable</string> <string name="pref_key_error_reports_enabled">acra.enable</string>
<string name="pref_title_error_reports">Enable automated error reports</string> <string name="pref_title_error_reports">Enable automated error reports</string>
<string name="pref_error_reports_off">Error reports will not be sent</string> <string name="pref_error_reports_off">Error reports will not be sent</string>
<string name="pref_error_reports_on">Error reports will be sent</string> <string name="pref_error_reports_on">Error reports will be sent</string>

View file

@ -19,7 +19,7 @@
android:title="@string/title_dark_mode" /> android:title="@string/title_dark_mode" />
<SwitchPreference <SwitchPreference
android:defaultValue="true" android:defaultValue="true"
android:key="@string/error_reports_enabled" android:key="@string/pref_key_error_reports_enabled"
android:summaryOff="@string/pref_error_reports_off" android:summaryOff="@string/pref_error_reports_off"
android:summaryOn="@string/pref_error_reports_on" android:summaryOn="@string/pref_error_reports_on"
android:title="@string/pref_title_error_reports" /> android:title="@string/pref_title_error_reports" />

View file

@ -9,7 +9,6 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:4.0.1'
classpath 'com.google.gms:google-services:4.3.3' 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" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }

View file

@ -1,4 +0,0 @@
defaults.url=https://sentry.brawner.dev/
defaults.project=simplemarkdown
defaults.org=wbrawner
auth.token=