package com.fox2code.mmm.utils.sentry; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import com.fox2code.mmm.CrashHandler; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.androidacy.AndroidacyUtil; import org.matomo.sdk.extra.TrackHelper; import java.util.Objects; import io.sentry.Sentry; import io.sentry.android.core.SentryAndroid; import io.sentry.android.fragment.FragmentLifecycleIntegration; import io.sentry.android.timber.SentryTimberIntegration; import timber.log.Timber; public class SentryMain { public static final boolean IS_SENTRY_INSTALLED = true; public static boolean isCrashing = false; private static boolean sentryEnabled = false; /** * Initialize Sentry * Sentry is used for crash reporting and performance monitoring. */ @SuppressLint({"RestrictedApi", "UnspecifiedImmutableFlag"}) public static void initialize(final MainApplication mainApplication) { Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> { isCrashing = true; TrackHelper.track().exception(throwable).with(MainApplication.getINSTANCE().getTracker()); SharedPreferences.Editor editor = MainApplication.getINSTANCE().getSharedPreferences("sentry", Context.MODE_PRIVATE).edit(); editor.putString("lastExitReason", "crash"); editor.putLong("lastExitTime", System.currentTimeMillis()); editor.putString("lastExitReason", "crash"); editor.putString("lastExitId", String.valueOf(Sentry.getLastEventId())); editor.apply(); Timber.e("Uncaught exception with sentry ID %s and stacktrace %s", Sentry.getLastEventId(), throwable.getStackTrace()); // open crash handler and exit Intent intent = new Intent(mainApplication, CrashHandler.class); // pass the entire exception to the crash handler intent.putExtra("exception", throwable); // add stacktrace as string intent.putExtra("stacktrace", throwable.getStackTrace()); // put lastEventId in intent (get from preferences) intent.putExtra("lastEventId", String.valueOf(Sentry.getLastEventId())); // serialize Sentry.captureException and pass it to the crash handler intent.putExtra("sentryException", throwable); // pass crashReportingEnabled to crash handler intent.putExtra("crashReportingEnabled", isSentryEnabled()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); Timber.e("Starting crash handler"); mainApplication.startActivity(intent); Timber.e("Exiting"); android.os.Process.killProcess(android.os.Process.myPid()); }); // If first_launch pref is not false, refuse to initialize Sentry SharedPreferences sharedPreferences = MainApplication.getSharedPreferences("mmm"); if (!Objects.equals(sharedPreferences.getString("last_shown_setup", null), "v1")) { return; } sentryEnabled = sharedPreferences.getBoolean("pref_crash_reporting_enabled", false); // set sentryEnabled on preference change of pref_crash_reporting_enabled sharedPreferences.registerOnSharedPreferenceChangeListener((sharedPreferences1, s) -> { if (s.equals("pref_crash_reporting_enabled")) { sentryEnabled = sharedPreferences1.getBoolean(s, false); } }); SentryAndroid.init(mainApplication, options -> { // If crash reporting is disabled, stop here. if (!MainApplication.isCrashReportingEnabled()) { sentryEnabled = false; // Set sentry state to disabled options.setDsn(""); } else { // get pref_crash_reporting_pii pref boolean crashReportingPii = sharedPreferences.getBoolean("crashReportingPii", false); sentryEnabled = true; // Set sentry state to enabled options.addIntegration(new FragmentLifecycleIntegration(mainApplication, true, true)); // Enable automatic activity lifecycle breadcrumbs options.setEnableActivityLifecycleBreadcrumbs(true); // Enable automatic fragment lifecycle breadcrumbs options.addIntegration(new SentryTimberIntegration()); options.setCollectAdditionalContext(true); options.setAttachThreads(true); options.setAttachStacktrace(true); options.setEnableNdk(true); options.addInAppInclude("com.fox2code.mmm"); options.addInAppInclude("com.fox2code.mmm.debug"); options.addInAppInclude("com.fox2code.mmm.fdroid"); options.addInAppExclude("com.fox2code.mmm.utils.sentry.SentryMain"); // Respect user preference for sending PII. default is true on non fdroid builds, false on fdroid builds options.setSendDefaultPii(crashReportingPii); options.enableAllAutoBreadcrumbs(true); // in-app screenshots are only sent if the app crashes, and it only shows the last activity. so no, we won't see your, ahem, "private" stuff options.setAttachScreenshot(true); // It just tell if sentry should ping the sentry dsn to tell the app is running. Useful for performance and profiling. options.setEnableAutoSessionTracking(true); // disable crash tracking - we handle that ourselves options.setEnableUncaughtExceptionHandler(false); // Add a callback that will be used before the event is sent to Sentry. // With this callback, you can modify the event or, when returning null, also discard the event. options.setBeforeSend((event, hint) -> { // in the rare event that crash reporting has been disabled since we started the app, we don't want to send the crash report if (!sentryEnabled) { return null; } if (isCrashing) { return null; } return event; }); // Filter breadcrumb content from crash report. options.setBeforeBreadcrumb((breadcrumb, hint) -> { String url = (String) breadcrumb.getData("url"); if (url == null || url.isEmpty()) return breadcrumb; if ("cloudflare-dns.com".equals(Uri.parse(url).getHost())) return null; if (AndroidacyUtil.isAndroidacyLink(url)) { breadcrumb.setData("url", AndroidacyUtil.hideToken(url)); } return breadcrumb; }); } }); } public static void addSentryBreadcrumb(SentryBreadcrumb sentryBreadcrumb) { if (MainApplication.isCrashReportingEnabled()) { Sentry.addBreadcrumb(sentryBreadcrumb.breadcrumb); } } @SuppressWarnings("unused") public static boolean isSentryEnabled() { return sentryEnabled; } }