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