mirror of
https://codeberg.org/OpenTracksApp/OpenTracks.git
synced 2026-10-03 01:53:05 +02:00
Add ExceptionHandler and ShowErrorActivity
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import de.dennisguse.opentracks.databinding.ActivityShowErrorBinding;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
|
||||
public class ShowErrorActivity extends AbstractActivity {
|
||||
|
||||
public static final String EXTRA_ERROR_TEXT = "error";
|
||||
|
||||
private ActivityShowErrorBinding viewBinding;
|
||||
|
||||
@Override
|
||||
public void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
viewBinding.textViewError.setText(getIntent().getStringExtra(EXTRA_ERROR_TEXT));
|
||||
viewBinding.bottomAppBarLayout.bottomAppBarTitle.setText(createErrorTitle());
|
||||
setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getRootView() {
|
||||
viewBinding = ActivityShowErrorBinding.inflate(getLayoutInflater());
|
||||
return viewBinding.getRoot();
|
||||
}
|
||||
|
||||
private String createErrorTitle() {
|
||||
return String.format(getString(R.string.error_crash_title), getString(R.string.app_name));
|
||||
}
|
||||
|
||||
private void reportBug() {
|
||||
Uri uriUrl;
|
||||
try {
|
||||
uriUrl = Uri.parse(
|
||||
String.format(
|
||||
getString(R.string.report_issue_link),
|
||||
URLEncoder.encode(viewBinding.textViewError.getText().toString(), StandardCharsets.UTF_8.toString())
|
||||
)
|
||||
);
|
||||
} catch (final UnsupportedEncodingException ignored) {
|
||||
// can't happen as UTF-8 is always available
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, uriUrl);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.show_error, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
if (item.getItemId() == R.id.error_share) {
|
||||
onClickedShare();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.error_report) {
|
||||
reportBug();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void onClickedShare() {
|
||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, createErrorTitle());
|
||||
intent.putExtra(Intent.EXTRA_TEXT, viewBinding.textViewError.getText());
|
||||
intent.setType("text/plain");
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package de.dennisguse.opentracks;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||
import de.dennisguse.opentracks.util.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* Code that is executed when the application starts.
|
||||
@@ -37,6 +43,36 @@ public class Startup extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(final Context base) {
|
||||
super.attachBaseContext(base);
|
||||
|
||||
// handle crashes only outside the crash reporter activity/process
|
||||
if (!isCrashReportingProcess()) {
|
||||
Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
ExceptionHandler crashReporter = new ExceptionHandler(this, defaultPlatformHandler);
|
||||
Thread.setDefaultUncaughtExceptionHandler(crashReporter);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCrashReportingProcess() {
|
||||
String processName = "";
|
||||
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
// Using the same technique as Application.getProcessName() for older devices
|
||||
// Using reflection since ActivityThread is an internal API
|
||||
try {
|
||||
@SuppressLint("PrivateApi")
|
||||
Class<?> 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.
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user