22
33import android .annotation .SuppressLint ;
44import android .content .Context ;
5+ import io .sentry .Hint ;
56import io .sentry .IHub ;
6- import io .sentry .ILogger ;
77import io .sentry .Integration ;
8+ import io .sentry .SentryEvent ;
89import io .sentry .SentryLevel ;
910import io .sentry .SentryOptions ;
1011import io .sentry .exception .ExceptionMechanismException ;
12+ import io .sentry .hints .AbnormalExit ;
1113import io .sentry .protocol .Mechanism ;
14+ import io .sentry .util .HintUtils ;
1215import io .sentry .util .Objects ;
1316import java .io .Closeable ;
1417import java .io .IOException ;
@@ -64,7 +67,7 @@ private void register(final @NotNull IHub hub, final @NotNull SentryAndroidOptio
6467 new ANRWatchDog (
6568 options .getAnrTimeoutIntervalMillis (),
6669 options .isAnrReportInDebug (),
67- error -> reportANR (hub , options . getLogger () , error ),
70+ error -> reportANR (hub , options , error ),
6871 options .getLogger (),
6972 context );
7073 anrWatchDog .start ();
@@ -78,16 +81,40 @@ private void register(final @NotNull IHub hub, final @NotNull SentryAndroidOptio
7881 @ TestOnly
7982 void reportANR (
8083 final @ NotNull IHub hub ,
81- final @ NotNull ILogger logger ,
84+ final @ NotNull SentryAndroidOptions options ,
8285 final @ NotNull ApplicationNotResponding error ) {
83- logger .log (SentryLevel .INFO , "ANR triggered with message: %s" , error .getMessage ());
86+ options . getLogger () .log (SentryLevel .INFO , "ANR triggered with message: %s" , error .getMessage ());
8487
88+ // if LifecycleWatcher isn't available, we always assume the ANR is foreground
89+ final boolean isAppInBackground = Boolean .TRUE .equals (AppState .getInstance ().isInBackground ());
90+
91+ @ SuppressWarnings ("ThrowableNotThrown" )
92+ final Throwable anrThrowable = buildAnrThrowable (isAppInBackground , options , error );
93+
94+ final SentryEvent event = new SentryEvent (anrThrowable );
95+ event .setLevel (SentryLevel .ERROR );
96+
97+ final AnrHint anrHint = new AnrHint (isAppInBackground );
98+ final Hint hint = HintUtils .createWithTypeCheckHint (anrHint );
99+
100+ hub .captureEvent (event , hint );
101+ }
102+
103+ private @ NotNull Throwable buildAnrThrowable (
104+ final boolean isAppInBackground ,
105+ final @ NotNull SentryAndroidOptions options ,
106+ final @ NotNull ApplicationNotResponding anr ) {
107+
108+ String message = "ANR for at least " + options .getAnrTimeoutIntervalMillis () + " ms." ;
109+ if (isAppInBackground ) {
110+ message = "Background " + message ;
111+ }
112+
113+ final ApplicationNotResponding error = new ApplicationNotResponding (message , anr .getThread ());
85114 final Mechanism mechanism = new Mechanism ();
86115 mechanism .setType ("ANR" );
87- final ExceptionMechanismException throwable =
88- new ExceptionMechanismException (mechanism , error , error .getThread (), true );
89116
90- hub . captureException ( throwable );
117+ return new ExceptionMechanismException ( mechanism , error , error . getThread (), true );
91118 }
92119
93120 @ TestOnly
@@ -108,4 +135,23 @@ public void close() throws IOException {
108135 }
109136 }
110137 }
138+
139+ /**
140+ * ANR is an abnormal session exit, according to <a
141+ * href="https://develop.sentry.dev/sdk/sessions/#crashed-abnormal-vs-errored">Develop Docs</a>
142+ * because we don't know whether the app has recovered after it or not.
143+ */
144+ static final class AnrHint implements AbnormalExit {
145+
146+ private final boolean isBackgroundAnr ;
147+
148+ AnrHint (final boolean isBackgroundAnr ) {
149+ this .isBackgroundAnr = isBackgroundAnr ;
150+ }
151+
152+ @ Override
153+ public String mechanism () {
154+ return isBackgroundAnr ? "anr_background" : "anr_foreground" ;
155+ }
156+ }
111157}
0 commit comments