diff options
author | pacien | 2018-03-31 16:56:35 +0200 |
---|---|---|
committer | pacien | 2018-03-31 16:56:35 +0200 |
commit | a5762d98a07ce30e8b3e1ac364e15e1c65029b75 (patch) | |
tree | 324096d5feffd396cef8660b3a1bcb53b16d7f15 /app/src/main/java/org/pacien | |
parent | 32a68afadec59da045118c6707cf4caec145502b (diff) | |
download | tincapp-a5762d98a07ce30e8b3e1ac364e15e1c65029b75.tar.gz |
Prompt for bug report
Diffstat (limited to 'app/src/main/java/org/pacien')
4 files changed, 75 insertions, 13 deletions
diff --git a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt index ff5b930..e90ada2 100644 --- a/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt +++ b/app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt | |||
@@ -13,6 +13,8 @@ import org.pacien.tincapp.BuildConfig | |||
13 | import org.pacien.tincapp.R | 13 | import org.pacien.tincapp.R |
14 | import org.pacien.tincapp.context.App | 14 | import org.pacien.tincapp.context.App |
15 | import org.pacien.tincapp.context.AppInfo | 15 | import org.pacien.tincapp.context.AppInfo |
16 | import org.pacien.tincapp.context.AppPaths | ||
17 | import org.pacien.tincapp.context.CrashRecorder | ||
16 | 18 | ||
17 | /** | 19 | /** |
18 | * @author pacien | 20 | * @author pacien |
@@ -22,6 +24,7 @@ abstract class BaseActivity : AppCompatActivity() { | |||
22 | super.onCreate(savedInstanceState) | 24 | super.onCreate(savedInstanceState) |
23 | setContentView(R.layout.base) | 25 | setContentView(R.layout.base) |
24 | setSupportActionBar(toolbar) | 26 | setSupportActionBar(toolbar) |
27 | handleRecentCrash() | ||
25 | } | 28 | } |
26 | 29 | ||
27 | override fun onCreateOptionsMenu(m: Menu): Boolean { | 30 | override fun onCreateOptionsMenu(m: Menu): Boolean { |
@@ -45,6 +48,26 @@ abstract class BaseActivity : AppCompatActivity() { | |||
45 | if (!isFinishing && !isDestroyed) super.runOnUiThread(action) | 48 | if (!isFinishing && !isDestroyed) super.runOnUiThread(action) |
46 | } | 49 | } |
47 | 50 | ||
51 | private fun handleRecentCrash() { | ||
52 | if (!CrashRecorder.hasPreviouslyCrashed()) return | ||
53 | CrashRecorder.dismissPreviousCrash() | ||
54 | |||
55 | AlertDialog.Builder(this) | ||
56 | .setTitle(R.string.title_app_crash) | ||
57 | .setMessage(listOf( | ||
58 | resources.getString(R.string.message_app_crash), | ||
59 | resources.getString(R.string.message_crash_logged, AppPaths.appLogFile().absolutePath) | ||
60 | ).joinToString("\n\n")) | ||
61 | .setNeutralButton(R.string.action_send_report, { _, _ -> | ||
62 | App.sendMail( | ||
63 | resources.getString(R.string.app_dev_email), | ||
64 | listOf(R.string.app_name, R.string.title_app_crash).joinToString(" / ", transform = resources::getString), | ||
65 | AppPaths.appLogFile().let { if (it.exists()) it.readText() else "" }) | ||
66 | }) | ||
67 | .setPositiveButton(R.string.action_close, { _, _ -> Unit }) | ||
68 | .show() | ||
69 | } | ||
70 | |||
48 | protected fun notify(@StringRes msg: Int) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show() | 71 | protected fun notify(@StringRes msg: Int) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show() |
49 | protected fun notify(msg: String) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show() | 72 | protected fun notify(msg: String) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show() |
50 | protected fun showProgressDialog(@StringRes msg: Int): ProgressDialog = ProgressDialog.show(this, null, getString(msg), true, false) | 73 | protected fun showProgressDialog(@StringRes msg: Int): ProgressDialog = ProgressDialog.show(this, null, getString(msg), true, false) |
diff --git a/app/src/main/java/org/pacien/tincapp/context/App.kt b/app/src/main/java/org/pacien/tincapp/context/App.kt index 53049f3..7ec1a47 100644 --- a/app/src/main/java/org/pacien/tincapp/context/App.kt +++ b/app/src/main/java/org/pacien/tincapp/context/App.kt | |||
@@ -9,31 +9,26 @@ import android.support.annotation.StringRes | |||
9 | import android.support.v7.app.AlertDialog | 9 | import android.support.v7.app.AlertDialog |
10 | import android.view.WindowManager | 10 | import android.view.WindowManager |
11 | import org.pacien.tincapp.R | 11 | import org.pacien.tincapp.R |
12 | import org.slf4j.Logger | ||
13 | import org.slf4j.LoggerFactory | 12 | import org.slf4j.LoggerFactory |
13 | import java.io.File | ||
14 | 14 | ||
15 | /** | 15 | /** |
16 | * @author pacien | 16 | * @author pacien |
17 | */ | 17 | */ |
18 | class App : Application(), Thread.UncaughtExceptionHandler { | 18 | class App : Application() { |
19 | private var logger: Logger? = null | ||
20 | private var systemCrashHandler: Thread.UncaughtExceptionHandler? = null | ||
21 | |||
22 | override fun onCreate() { | 19 | override fun onCreate() { |
23 | super.onCreate() | 20 | super.onCreate() |
24 | appContext = applicationContext | 21 | appContext = applicationContext |
25 | handler = Handler() | 22 | handler = Handler() |
26 | |||
27 | AppLogger.configure() | 23 | AppLogger.configure() |
28 | logger = LoggerFactory.getLogger(this.javaClass) | 24 | setupCrashHandler() |
29 | |||
30 | systemCrashHandler = Thread.getDefaultUncaughtExceptionHandler() | ||
31 | Thread.setDefaultUncaughtExceptionHandler(this) | ||
32 | } | 25 | } |
33 | 26 | ||
34 | override fun uncaughtException(thread: Thread, throwable: Throwable) { | 27 | private fun setupCrashHandler() { |
35 | logger?.error("Fatal application error.", throwable) | 28 | val logger = LoggerFactory.getLogger(this.javaClass) |
36 | systemCrashHandler?.uncaughtException(thread, throwable) | 29 | val systemCrashHandler = Thread.getDefaultUncaughtExceptionHandler() |
30 | val crashRecorder = CrashRecorder(logger, systemCrashHandler) | ||
31 | Thread.setDefaultUncaughtExceptionHandler(crashRecorder) | ||
37 | } | 32 | } |
38 | 33 | ||
39 | companion object { | 34 | companion object { |
@@ -56,5 +51,16 @@ class App : Application(), Thread.UncaughtExceptionHandler { | |||
56 | val chooser = Intent.createChooser(intent, getResources().getString(R.string.action_open_web_page)) | 51 | val chooser = Intent.createChooser(intent, getResources().getString(R.string.action_open_web_page)) |
57 | appContext?.startActivity(chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) | 52 | appContext?.startActivity(chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) |
58 | } | 53 | } |
54 | |||
55 | fun sendMail(recipient: String, subject: String, body: String? = null, attachment: File? = null) { | ||
56 | val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) | ||
57 | .putExtra(Intent.EXTRA_EMAIL, arrayOf(recipient)) | ||
58 | .putExtra(Intent.EXTRA_SUBJECT, subject) | ||
59 | .apply { if (body != null) putExtra(Intent.EXTRA_TEXT, body) } | ||
60 | .apply { if (attachment != null) putExtra(Intent.EXTRA_STREAM, Uri.fromFile(attachment)) } | ||
61 | |||
62 | val chooser = Intent.createChooser(intent, getResources().getString(R.string.action_send_email)) | ||
63 | appContext?.startActivity(chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) | ||
64 | } | ||
59 | } | 65 | } |
60 | } | 66 | } |
diff --git a/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt b/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt index 4b36dfe..3b84a69 100644 --- a/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt +++ b/app/src/main/java/org/pacien/tincapp/context/AppPaths.kt | |||
@@ -14,6 +14,7 @@ object AppPaths { | |||
14 | private const val TINC_BIN = "libtinc.so" | 14 | private const val TINC_BIN = "libtinc.so" |
15 | 15 | ||
16 | private const val APPLOG_FILE = "tincapp.log" | 16 | private const val APPLOG_FILE = "tincapp.log" |
17 | private const val CRASHFLAG_FILE = "crash.flag" | ||
17 | private const val LOGFILE_FORMAT = "tinc.%s.log" | 18 | private const val LOGFILE_FORMAT = "tinc.%s.log" |
18 | private const val PIDFILE_FORMAT = "tinc.%s.pid" | 19 | private const val PIDFILE_FORMAT = "tinc.%s.pid" |
19 | 20 | ||
@@ -27,6 +28,7 @@ object AppPaths { | |||
27 | fun storageAvailable() = | 28 | fun storageAvailable() = |
28 | Environment.getExternalStorageState().let { it == Environment.MEDIA_MOUNTED && it != Environment.MEDIA_MOUNTED_READ_ONLY } | 29 | Environment.getExternalStorageState().let { it == Environment.MEDIA_MOUNTED && it != Environment.MEDIA_MOUNTED_READ_ONLY } |
29 | 30 | ||
31 | fun internalCacheDir() = App.getContext().cacheDir | ||
30 | fun cacheDir() = App.getContext().externalCacheDir | 32 | fun cacheDir() = App.getContext().externalCacheDir |
31 | fun confDir() = App.getContext().getExternalFilesDir(null) | 33 | fun confDir() = App.getContext().getExternalFilesDir(null) |
32 | fun binDir() = File(App.getContext().applicationInfo.nativeLibraryDir) | 34 | fun binDir() = File(App.getContext().applicationInfo.nativeLibraryDir) |
@@ -39,6 +41,7 @@ object AppPaths { | |||
39 | fun logFile(netName: String) = File(cacheDir(), String.format(LOGFILE_FORMAT, netName)) | 41 | fun logFile(netName: String) = File(cacheDir(), String.format(LOGFILE_FORMAT, netName)) |
40 | fun pidFile(netName: String) = File(App.getContext().cacheDir, String.format(PIDFILE_FORMAT, netName)) | 42 | fun pidFile(netName: String) = File(App.getContext().cacheDir, String.format(PIDFILE_FORMAT, netName)) |
41 | fun appLogFile() = File(cacheDir(), APPLOG_FILE) | 43 | fun appLogFile() = File(cacheDir(), APPLOG_FILE) |
44 | fun crashFlagFile() = File(internalCacheDir(), CRASHFLAG_FILE) | ||
42 | 45 | ||
43 | fun existing(f: File) = f.apply { if (!exists()) throw FileNotFoundException(f.absolutePath) } | 46 | fun existing(f: File) = f.apply { if (!exists()) throw FileNotFoundException(f.absolutePath) } |
44 | 47 | ||
diff --git a/app/src/main/java/org/pacien/tincapp/context/CrashRecorder.kt b/app/src/main/java/org/pacien/tincapp/context/CrashRecorder.kt new file mode 100644 index 0000000..1e8556a --- /dev/null +++ b/app/src/main/java/org/pacien/tincapp/context/CrashRecorder.kt | |||
@@ -0,0 +1,30 @@ | |||
1 | package org.pacien.tincapp.context | ||
2 | |||
3 | import org.slf4j.Logger | ||
4 | |||
5 | /** | ||
6 | * @author pacien | ||
7 | */ | ||
8 | class CrashRecorder(private val logger: Logger, | ||
9 | private val upstreamCrashHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler { | ||
10 | |||
11 | companion object { | ||
12 | private val flagFile = AppPaths.crashFlagFile() | ||
13 | |||
14 | fun hasPreviouslyCrashed() = flagFile.exists() | ||
15 | |||
16 | fun flagCrash() { | ||
17 | flagFile.apply { if (!exists()) createNewFile() } | ||
18 | } | ||
19 | |||
20 | fun dismissPreviousCrash() { | ||
21 | flagFile.delete() | ||
22 | } | ||
23 | } | ||
24 | |||
25 | override fun uncaughtException(thread: Thread, throwable: Throwable) { | ||
26 | logger.error("Fatal application error.", throwable) | ||
27 | flagCrash() | ||
28 | upstreamCrashHandler.uncaughtException(thread, throwable) | ||
29 | } | ||
30 | } | ||