diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 4726f5997..71ce65841 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -364,6 +364,14 @@ limitations under the License.
+
+
activityThread = Class.forName("android.app.ActivityThread");
+ @SuppressLint("DiscouragedPrivateApi") Method getProcessName = activityThread.getDeclaredMethod("currentProcessName");
+ processName = (String) getProcessName.invoke(null);
+ } catch (Exception ignored) {
+ }
+ } else {
+ processName = Application.getProcessName();
+ }
+ return processName != null && processName.endsWith(":crash");
+ }
+
/**
* Returns the name of the database used by SQLiteOpenHelper.
* See {@link android.database.sqlite.SQLiteOpenHelper} for details.
diff --git a/src/main/java/de/dennisguse/opentracks/util/ExceptionHandler.java b/src/main/java/de/dennisguse/opentracks/util/ExceptionHandler.java
new file mode 100644
index 000000000..e7af76493
--- /dev/null
+++ b/src/main/java/de/dennisguse/opentracks/util/ExceptionHandler.java
@@ -0,0 +1,80 @@
+package de.dennisguse.opentracks.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import de.dennisguse.opentracks.BuildConfig;
+import de.dennisguse.opentracks.ShowErrorActivity;
+
+public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ private final Context context;
+ private final Thread.UncaughtExceptionHandler defaultExceptionHandler;
+
+ public ExceptionHandler(final Context context, final Thread.UncaughtExceptionHandler defaultExceptionHandler) {
+ this.context = context;
+ this.defaultExceptionHandler = defaultExceptionHandler;
+ }
+
+ @Override
+ public void uncaughtException(@NonNull final Thread thread, @NonNull final Throwable exception) {
+ try {
+ String errorReport = generateErrorReport(formatException(thread, exception));
+ Intent intent = new Intent(context, ShowErrorActivity.class);
+ intent.putExtra(ShowErrorActivity.EXTRA_ERROR_TEXT, errorReport);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ context.startActivity(intent);
+ // Pass exception to OS for graceful handling - OS will report it via ADB
+ // and close all activities and services.
+ defaultExceptionHandler.uncaughtException(thread, exception);
+ } catch (final Exception fatalException) {
+ // do not recurse into custom handler if exception is thrown during
+ // exception handling. Pass this ultimate fatal exception to OS
+ defaultExceptionHandler.uncaughtException(thread, fatalException);
+ }
+ }
+
+ private String formatException(final Thread thread, final Throwable exception) {
+ StringBuilder stringBuilder = new StringBuilder();
+ stringBuilder.append(String.format("Exception in thread \"%s\": ", thread.getName()));
+
+ // print available stacktrace
+ Writer writer = new StringWriter();
+ exception.printStackTrace(new PrintWriter(writer));
+ stringBuilder.append(writer);
+
+ return stringBuilder.toString();
+ }
+
+ private String generateErrorReport(final String stackTrace) {
+ return
+ "### App information\n" +
+ "* ID: " + BuildConfig.APPLICATION_ID + "\n" +
+ "* Version: " + BuildConfig.VERSION_CODE + " " + BuildConfig.VERSION_NAME + "\n" +
+ "\n" +
+ "### Device information\n" +
+ "* Brand: " + Build.BRAND + "\n" +
+ "* Device: " + Build.DEVICE + "\n" +
+ "* Model: " + Build.MODEL + "\n" +
+ "* Id: " + Build.ID + "\n" +
+ "* Product: " + Build.PRODUCT + "\n" +
+ "\n" +
+ "### Firmware\n" +
+ "* SDK: " + Build.VERSION.SDK_INT + "\n" +
+ "* Release: " + Build.VERSION.RELEASE + "\n" +
+ "* Incremental: " + Build.VERSION.INCREMENTAL + "\n" +
+ "\n" +
+ "### Cause of error\n" +
+ "```java\n" +
+ stackTrace +
+ "```\n";
+ }
+
+}
diff --git a/src/main/res/drawable/ic_bug_report_24dp.xml b/src/main/res/drawable/ic_bug_report_24dp.xml
new file mode 100644
index 000000000..1acb59f96
--- /dev/null
+++ b/src/main/res/drawable/ic_bug_report_24dp.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/main/res/layout/activity_show_error.xml b/src/main/res/layout/activity_show_error.xml
new file mode 100644
index 000000000..15594187d
--- /dev/null
+++ b/src/main/res/layout/activity_show_error.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/menu/show_error.xml b/src/main/res/menu/show_error.xml
new file mode 100644
index 000000000..1932905cd
--- /dev/null
+++ b/src/main/res/menu/show_error.xml
@@ -0,0 +1,21 @@
+
+
diff --git a/src/main/res/values/do_not_translate.xml b/src/main/res/values/do_not_translate.xml
index c43d744ef..1cb642276 100644
--- a/src/main/res/values/do_not_translate.xml
+++ b/src/main/res/values/do_not_translate.xml
@@ -44,6 +44,7 @@ limitations under the License.
w
-
+ https://github.com/OpenTracksApp/OpenTracks/issues/new?labels=bug&body=%1$s
- @string/waypoint_type_atm
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 60dfce470..8590ba1ba 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -666,4 +666,7 @@ limitations under the License.
Secondary
Remove
No compatible file manager installed
+ %1$s crashed
+ Share crash report
+ Report bug