aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/java/org/pacien/tincapp/activities/BaseActivity.kt23
-rw-r--r--app/src/main/java/org/pacien/tincapp/context/App.kt32
-rw-r--r--app/src/main/java/org/pacien/tincapp/context/AppPaths.kt3
-rw-r--r--app/src/main/java/org/pacien/tincapp/context/CrashRecorder.kt30
-rw-r--r--app/src/main/res/menu/menu_conf.xml14
-rw-r--r--app/src/main/res/values/strings.xml6
6 files changed, 81 insertions, 27 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
13import org.pacien.tincapp.R 13import org.pacien.tincapp.R
14import org.pacien.tincapp.context.App 14import org.pacien.tincapp.context.App
15import org.pacien.tincapp.context.AppInfo 15import org.pacien.tincapp.context.AppInfo
16import org.pacien.tincapp.context.AppPaths
17import 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
9import android.support.v7.app.AlertDialog 9import android.support.v7.app.AlertDialog
10import android.view.WindowManager 10import android.view.WindowManager
11import org.pacien.tincapp.R 11import org.pacien.tincapp.R
12import org.slf4j.Logger
13import org.slf4j.LoggerFactory 12import org.slf4j.LoggerFactory
13import java.io.File
14 14
15/** 15/**
16 * @author pacien 16 * @author pacien
17 */ 17 */
18class App : Application(), Thread.UncaughtExceptionHandler { 18class 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 @@
1package org.pacien.tincapp.context
2
3import org.slf4j.Logger
4
5/**
6 * @author pacien
7 */
8class 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}
diff --git a/app/src/main/res/menu/menu_conf.xml b/app/src/main/res/menu/menu_conf.xml
deleted file mode 100644
index df81b00..0000000
--- a/app/src/main/res/menu/menu_conf.xml
+++ /dev/null
@@ -1,14 +0,0 @@
1<menu xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:app="http://schemas.android.com/apk/res-auto"
3 xmlns:tools="http://schemas.android.com/tools"
4 tools:context="org.pacien.tincapp.activities.ConfigureActivity">
5
6 <item
7 android:id="@+id/menu_open_config_dir"
8 android:icon="@drawable/ic_folder_primary_24dp"
9 android:onClick="openConfigDir"
10 android:tint="@color/colorAccent"
11 android:title="@string/menu_open_config_dir"
12 app:showAsAction="ifRoom"/>
13
14</menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index da3fa10..cab09ee 100644
--- a/app/src/main/res/values/strings.xml
+++ b/