From 49d6acee550963befb3c3daf2fe3926c9c291d33 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 13 May 2024 14:04:18 +0200 Subject: [PATCH] refactor crash reporter. collect stats on crash; not send --- .../services/XmppConnectionService.java | 1 - .../settings/MainSettingsFragment.java | 2 +- .../conversations/utils/ExceptionHandler.java | 69 ++++++---- .../conversations/utils/ExceptionHelper.java | 125 ++++++++---------- 4 files changed, 104 insertions(+), 93 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cff35a97dd852140353af52fa362af6c0b3a3af1..80b4d6ddde600f4e4bc3b2a757921a77567ca0c5 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1261,7 +1261,6 @@ public class XmppConnectionService extends Service { toggleForegroundService(); this.destroyed = false; OmemoSetting.load(this); - ExceptionHelper.init(getApplicationContext()); try { Security.insertProviderAt(Conscrypt.newProvider(), 1); } catch (Throwable throwable) { diff --git a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java index 4ab8ade3ceb472b2762017306aacda450ab2e82c..0373d715fdf9aa2aa690406b8be038abe86913a8 100644 --- a/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/fragment/settings/MainSettingsFragment.java @@ -16,7 +16,7 @@ public class MainSettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { setPreferencesFromResource(R.xml.preferences_main, rootKey); - final var about = findPreference("about"); + final var about = findPreference("about2"); final var connection = findPreference("connection"); if (about == null || connection == null) { throw new IllegalStateException( diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java index 8a77e04bb884b4441e5b9d40a9e819a0873bee9c..c585ad38fd8293cbc24742d9b583791e3b8095f2 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java @@ -1,36 +1,59 @@ package eu.siacs.conversations.utils; import android.content.Context; +import android.os.Build; import androidx.annotation.NonNull; +import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; + +import eu.siacs.conversations.BuildConfig; +import eu.siacs.conversations.services.NotificationService; + +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; - -import eu.siacs.conversations.services.NotificationService; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.Locale; public class ExceptionHandler implements UncaughtExceptionHandler { - private final UncaughtExceptionHandler defaultHandler; - private final Context context; - - ExceptionHandler(final Context context) { - this.context = context; - this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - } - - @Override - public void uncaughtException(@NonNull Thread thread, final Throwable throwable) { - NotificationService.cancelIncomingCallNotification(context); - final Writer stringWriter = new StringWriter(); - final PrintWriter printWriter = new PrintWriter(stringWriter); - throwable.printStackTrace(printWriter); - final String stacktrace = stringWriter.toString(); - printWriter.close(); - ExceptionHelper.writeToStacktraceFile(context, stacktrace); - this.defaultHandler.uncaughtException(thread, throwable); - } - + private static final SimpleDateFormat DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ENGLISH); + + private final UncaughtExceptionHandler defaultHandler; + private final Context context; + + ExceptionHandler(final Context context) { + this.context = context; + this.defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + } + + @Override + public void uncaughtException(@NonNull Thread thread, final Throwable throwable) { + NotificationService.cancelIncomingCallNotification(context); + final String stacktrace; + try (final StringWriter stringWriter = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(stringWriter)) { + throwable.printStackTrace(printWriter); + stacktrace = stringWriter.toString(); + } catch (final IOException e) { + return; + } + final List report = + ImmutableList.of( + String.format( + "Version: %s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME), + String.format("Manufacturer: %s", Strings.nullToEmpty(Build.MANUFACTURER)), + String.format("Device: %s", Strings.nullToEmpty(Build.DEVICE)), + String.format("Timestamp: %s", DATE_FORMAT.format(new Date())), + stacktrace); + ExceptionHelper.writeToStacktraceFile(context, Joiner.on("\n").join(report)); + this.defaultHandler.uncaughtException(thread, throwable); + } } diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java index a804f013b99d8323bc10b862c999cbc64e98efeb..f2c23aa8d8a2f51ea1fc436c034435002ae2c6a5 100644 --- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java @@ -1,12 +1,12 @@ package eu.siacs.conversations.utils; import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; import android.util.Log; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.base.Charsets; +import com.google.common.io.CharSink; +import com.google.common.io.Files; import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.Config; @@ -17,19 +17,15 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.XmppActivity; -import java.io.BufferedReader; -import java.io.FileInputStream; +import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; import java.io.OutputStream; import java.text.SimpleDateFormat; -import java.util.Date; import java.util.Locale; public class ExceptionHelper { private static final String FILENAME = "stacktrace.txt"; - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); public static void init(final Context context) { if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) { @@ -39,72 +35,65 @@ public class ExceptionHelper { } public static boolean checkForCrash(final XmppActivity activity) { + final XmppConnectionService service = + activity == null ? null : activity.xmppConnectionService; + if (service == null) { + return false; + } + final AppSettings appSettings = new AppSettings(activity); + if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) { + return false; + } + final Account account = AccountUtils.getFirstEnabled(service); + if (account == null) { + return false; + } + final var file = new File(activity.getCacheDir(), FILENAME); + if (!file.exists()) { + return false; + } + final String report; try { - final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService; - if (service == null) { - return false; - } - final AppSettings appSettings = new AppSettings(activity); - if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) { - return false; - } - final Account account = AccountUtils.getFirstEnabled(service); - if (account == null) { - return false; - } - final FileInputStream file = activity.openFileInput(FILENAME); - final InputStreamReader inputStreamReader = new InputStreamReader(file); - final BufferedReader stacktrace = new BufferedReader(inputStreamReader); - final StringBuilder report = new StringBuilder(); - final PackageManager pm = activity.getPackageManager(); - final PackageInfo packageInfo; - try { - packageInfo = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES); - final String versionName = packageInfo.versionName; - final int versionCode = packageInfo.versionCode; - final int version = versionCode > 10000 ? (versionCode / 100) : versionCode; - report.append(String.format(Locale.ROOT, "Version: %s(%d)", versionName, version)).append('\n'); - report.append("Last Update: ").append(DATE_FORMAT.format(new Date(packageInfo.lastUpdateTime))).append('\n'); - Signature[] signatures = packageInfo.signatures; - if (signatures != null && signatures.length >= 1) { - report.append("SHA-1: ").append(CryptoHelper.getFingerprintCert(packageInfo.signatures[0].toByteArray())).append('\n'); - } - report.append('\n'); - } catch (final Exception e) { - return false; - } - String line; - while ((line = stacktrace.readLine()) != null) { - report.append(line); - report.append('\n'); - } - file.close(); - activity.deleteFile(FILENAME); - final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); - builder.setTitle(activity.getString(R.string.crash_report_title, activity.getString(R.string.app_name))); - builder.setMessage(activity.getString(R.string.crash_report_message, activity.getString(R.string.app_name))); - builder.setPositiveButton(activity.getText(R.string.send_now), (dialog, which) -> { - - Log.d(Config.LOGTAG, "using account=" + account.getJid().asBareJid() + " to send in stack trace"); - Conversation conversation = service.findOrCreateConversation(account, Config.BUG_REPORTS, false, true); - Message message = new Message(conversation, report.toString(), Message.ENCRYPTION_NONE); - service.sendMessage(message); - }); - builder.setNegativeButton(activity.getText(R.string.send_never), (dialog, which) -> appSettings.setSendCrashReports(false)); - builder.create().show(); - return true; - } catch (final IOException ignored) { + report = Files.asCharSource(file, Charsets.UTF_8).read(); + } catch (final IOException e) { return false; } + if (file.delete()) { + Log.d(Config.LOGTAG, "deleted crash report file"); + } + final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); + builder.setTitle( + activity.getString( + R.string.crash_report_title, activity.getString(R.string.app_name))); + builder.setMessage( + activity.getString( + R.string.crash_report_message, activity.getString(R.string.app_name))); + builder.setPositiveButton( + activity.getText(R.string.send_now), + (dialog, which) -> { + Log.d( + Config.LOGTAG, + "using account=" + + account.getJid().asBareJid() + + " to send in stack trace"); + Conversation conversation = + service.findOrCreateConversation( + account, Config.BUG_REPORTS, false, true); + Message message = new Message(conversation, report, Message.ENCRYPTION_NONE); + service.sendMessage(message); + }); + builder.setNegativeButton( + activity.getText(R.string.send_never), + (dialog, which) -> appSettings.setSendCrashReports(false)); + builder.create().show(); + return true; } - static void writeToStacktraceFile(Context context, String msg) { + static void writeToStacktraceFile(final Context context, final String msg) { try { - OutputStream os = context.openFileOutput(FILENAME, Context.MODE_PRIVATE); - os.write(msg.getBytes()); - os.flush(); - os.close(); - } catch (IOException ignored) { + Files.asCharSink(new File(context.getCacheDir(), FILENAME), Charsets.UTF_8).write(msg); + } catch (IOException e) { + Log.w(Config.LOGTAG, "could not write stack trace to file", e); } } }