1package eu.siacs.conversations.utils;
2
3import android.content.Context;
4import android.util.Log;
5
6import com.google.android.material.dialog.MaterialAlertDialogBuilder;
7import com.google.common.base.Charsets;
8import com.google.common.io.CharSink;
9import com.google.common.io.Files;
10
11import eu.siacs.conversations.AppSettings;
12import eu.siacs.conversations.Config;
13import eu.siacs.conversations.R;
14import eu.siacs.conversations.entities.Account;
15import eu.siacs.conversations.entities.Conversation;
16import eu.siacs.conversations.entities.Message;
17import eu.siacs.conversations.services.XmppConnectionService;
18import eu.siacs.conversations.ui.XmppActivity;
19
20import java.io.File;
21import java.io.IOException;
22import java.io.OutputStream;
23import java.lang.ClassNotFoundException;
24import java.text.SimpleDateFormat;
25import java.util.Locale;
26
27public class ExceptionHelper {
28
29 private static final String FILENAME = "stacktrace.txt";
30
31 public static void init(final Context context) {
32 if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) {
33 return;
34 }
35 Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(context));
36 }
37
38 public static boolean checkForCrash(final XmppActivity activity) {
39 try {
40 Class.forName("io.sentry.Sentry");
41 return false;
42 } catch (final ClassNotFoundException e) { }
43
44 final XmppConnectionService service =
45 activity == null ? null : activity.xmppConnectionService;
46 if (service == null) {
47 return false;
48 }
49 final AppSettings appSettings = new AppSettings(activity);
50 if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) {
51 return false;
52 }
53 final Account account = AccountUtils.getFirstEnabled(service);
54 if (account == null) {
55 return false;
56 }
57 final var file = new File(activity.getCacheDir(), FILENAME);
58 if (!file.exists()) {
59 return false;
60 }
61 final String report;
62 try {
63 report = Files.asCharSource(file, Charsets.UTF_8).read();
64 } catch (final IOException e) {
65 return false;
66 }
67 if (file.delete()) {
68 Log.d(Config.LOGTAG, "deleted crash report file");
69 }
70 final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
71 builder.setTitle(
72 activity.getString(
73 R.string.crash_report_title, activity.getString(R.string.app_name)));
74 builder.setMessage(
75 activity.getString(
76 R.string.crash_report_message, activity.getString(R.string.app_name)));
77 builder.setPositiveButton(
78 activity.getText(R.string.send_now),
79 (dialog, which) -> {
80 Log.d(
81 Config.LOGTAG,
82 "using account="
83 + account.getJid().asBareJid()
84 + " to send in stack trace");
85 Conversation conversation =
86 service.findOrCreateConversation(
87 account, Config.BUG_REPORTS, false, true);
88 Message message = new Message(conversation, report, Message.ENCRYPTION_NONE);
89 service.sendMessage(message);
90 });
91 builder.setNegativeButton(
92 activity.getText(R.string.send_never),
93 (dialog, which) -> appSettings.setSendCrashReports(false));
94 builder.create().show();
95 return true;
96 }
97
98 static void writeToStacktraceFile(final Context context, final String msg) {
99 try {
100 Files.asCharSink(new File(context.getCacheDir(), FILENAME), Charsets.UTF_8).write(msg);
101 } catch (IOException e) {
102 Log.w(Config.LOGTAG, "could not write stack trace to file", e);
103 }
104 }
105}