From a5762d98a07ce30e8b3e1ac364e15e1c65029b75 Mon Sep 17 00:00:00 2001 From: pacien Date: Sat, 31 Mar 2018 16:56:35 +0200 Subject: Prompt for bug report --- .../org/pacien/tincapp/activities/BaseActivity.kt | 23 ++++++++++++++++ .../main/java/org/pacien/tincapp/context/App.kt | 32 +++++++++++++--------- .../java/org/pacien/tincapp/context/AppPaths.kt | 3 ++ .../org/pacien/tincapp/context/CrashRecorder.kt | 30 ++++++++++++++++++++ app/src/main/res/menu/menu_conf.xml | 14 ---------- app/src/main/res/values/strings.xml | 6 ++++ 6 files changed, 81 insertions(+), 27 deletions(-) create mode 100644 app/src/main/java/org/pacien/tincapp/context/CrashRecorder.kt delete mode 100644 app/src/main/res/menu/menu_conf.xml 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 import org.pacien.tincapp.R import org.pacien.tincapp.context.App import org.pacien.tincapp.context.AppInfo +import org.pacien.tincapp.context.AppPaths +import org.pacien.tincapp.context.CrashRecorder /** * @author pacien @@ -22,6 +24,7 @@ abstract class BaseActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.base) setSupportActionBar(toolbar) + handleRecentCrash() } override fun onCreateOptionsMenu(m: Menu): Boolean { @@ -45,6 +48,26 @@ abstract class BaseActivity : AppCompatActivity() { if (!isFinishing && !isDestroyed) super.runOnUiThread(action) } + private fun handleRecentCrash() { + if (!CrashRecorder.hasPreviouslyCrashed()) return + CrashRecorder.dismissPreviousCrash() + + AlertDialog.Builder(this) + .setTitle(R.string.title_app_crash) + .setMessage(listOf( + resources.getString(R.string.message_app_crash), + resources.getString(R.string.message_crash_logged, AppPaths.appLogFile().absolutePath) + ).joinToString("\n\n")) + .setNeutralButton(R.string.action_send_report, { _, _ -> + App.sendMail( + resources.getString(R.string.app_dev_email), + listOf(R.string.app_name, R.string.title_app_crash).joinToString(" / ", transform = resources::getString), + AppPaths.appLogFile().let { if (it.exists()) it.readText() else "" }) + }) + .setPositiveButton(R.string.action_close, { _, _ -> Unit }) + .show() + } + protected fun notify(@StringRes msg: Int) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show() protected fun notify(msg: String) = Snackbar.make(activity_base, msg, Snackbar.LENGTH_LONG).show() 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 import android.support.v7.app.AlertDialog import android.view.WindowManager import org.pacien.tincapp.R -import org.slf4j.Logger import org.slf4j.LoggerFactory +import java.io.File /** * @author pacien */ -class App : Application(), Thread.UncaughtExceptionHandler { - private var logger: Logger? = null - private var systemCrashHandler: Thread.UncaughtExceptionHandler? = null - +class App : Application() { override fun onCreate() { super.onCreate() appContext = applicationContext handler = Handler() - AppLogger.configure() - logger = LoggerFactory.getLogger(this.javaClass) - - systemCrashHandler = Thread.getDefaultUncaughtExceptionHandler() - Thread.setDefaultUncaughtExceptionHandler(this) + setupCrashHandler() } - override fun uncaughtException(thread: Thread, throwable: Throwable) { - logger?.error("Fatal application error.", throwable) - systemCrashHandler?.uncaughtException(thread, throwable) + private fun setupCrashHandler() { + val logger = LoggerFactory.getLogger(this.javaClass) + val systemCrashHandler = Thread.getDefaultUncaughtExceptionHandler() + val crashRecorder = CrashRecorder(logger, systemCrashHandler) + Thread.setDefaultUncaughtExceptionHandler(crashRecorder) } companion object { @@ -56,5 +51,16 @@ class App : Application(), Thread.UncaughtExceptionHandler { val chooser = Intent.createChooser(intent, getResources().getString(R.string.action_open_web_page)) appContext?.startActivity(chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) } + + fun sendMail(recipient: String, subject: String, body: String? = null, attachment: File? = null) { + val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) + .putExtra(Intent.EXTRA_EMAIL, arrayOf(recipient)) + .putExtra(Intent.EXTRA_SUBJECT, subject) + .apply { if (body != null) putExtra(Intent.EXTRA_TEXT, body) } + .apply { if (attachment != null) putExtra(Intent.EXTRA_STREAM, Uri.fromFile(attachment)) } + + val chooser = Intent.createChooser(intent, getResources().getString(R.string.action_send_email)) + appContext?.startActivity(chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + } } } 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 { private const val TINC_BIN = "libtinc.so" private const val APPLOG_FILE = "tincapp.log" + private const val CRASHFLAG_FILE = "crash.flag" private const val LOGFILE_FORMAT = "tinc.%s.log" private const val PIDFILE_FORMAT = "tinc.%s.pid" @@ -27,6 +28,7 @@ object AppPaths { fun storageAvailable() = Environment.getExternalStorageState().let { it == Environment.MEDIA_MOUNTED && it != Environment.MEDIA_MOUNTED_READ_ONLY } + fun internalCacheDir() = App.getContext().cacheDir fun cacheDir() = App.getContext().externalCacheDir fun confDir() = App.getContext().getExternalFilesDir(null) fun binDir() = File(App.getContext().applicationInfo.nativeLibraryDir) @@ -39,6 +41,7 @@ object AppPaths { fun logFile(netName: String) = File(cacheDir(), String.format(LOGFILE_FORMAT, netName)) fun pidFile(netName: String) = File(App.getContext().cacheDir, String.format(PIDFILE_FORMAT, netName)) fun appLogFile() = File(cacheDir(), APPLOG_FILE) + fun crashFlagFile() = File(internalCacheDir(), CRASHFLAG_FILE) fun existing(f: File) = f.apply { if (!exists()) throw FileNotFoundException(f.absolutePath) } 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 @@ +package org.pacien.tincapp.context + +import org.slf4j.Logger + +/** + * @author pacien + */ +class CrashRecorder(private val logger: Logger, + private val upstreamCrashHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler { + + companion object { + private val flagFile = AppPaths.crashFlagFile() + + fun hasPreviouslyCrashed() = flagFile.exists() + + fun flagCrash() { + flagFile.apply { if (!exists()) createNewFile() } + } + + fun dismissPreviousCrash() { + flagFile.delete() + } + } + + override fun uncaughtException(thread: Thread, throwable: Throwable) { + logger.error("Fatal application error.", throwable) + flagCrash() + upstreamCrashHandler.uncaughtException(thread, throwable) + } +} 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 @@ - - - - - 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/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Software distributed under the terms of the GNU General Public License v3. http://tincapp.pacien.org http://tincapp.pacien.org/doc.html#%1$s + pacien.gplayfr@gmail.com App version %1$s (%2$s build) Running on Android %1$s %2$s @@ -50,6 +51,7 @@ Unable to start tinc Private keys encryption Unlock tinc private keys + App crash Close Cancel @@ -66,6 +68,8 @@ Encrypt or decrypt private keys Dismiss Open web page + Send e-mail + Send report No network configuration has been found. Generating node configuration… @@ -89,6 +93,8 @@ Could not apply network interface configuration:\n\n%1$s Could not bind network interface. Is another VPN running? Invalid network name. + The application has previously encountered a fatal error. + The crash details have been saved in \"%1$s\". none yes -- cgit v1.2.3