From 302ba2ac66eac82a73bd40f260aaf0b9a58c8925 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Tue, 10 Nov 2026 16:29:02 +0100 Subject: [PATCH 1/5] ref(android): Extract ANR culprit exceptions into AnrCulpritEventHelper --- .../api/sentry-android-core.api | 6 ++ .../android/core/AnrCulpritEventHelper.java | 74 ++++++++++++++++ .../ApplicationExitInfoEventProcessor.java | 55 ++---------- .../android/core/AnrCulpritEventHelperTest.kt | 85 +++++++++++++++++++ 4 files changed, 174 insertions(+), 46 deletions(-) create mode 100644 sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java create mode 100644 sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index 712f981ecd..a896744f40 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -147,6 +147,12 @@ public final class io/sentry/android/core/AndroidSocketTagger : io/sentry/ISocke public fun untagSockets ()V } +public final class io/sentry/android/core/AnrCulpritEventHelper { + public fun (Lio/sentry/SentryExceptionFactory;)V + public fun createExceptions (Lio/sentry/android/core/anr/AggregatedStackTrace;Lio/sentry/protocol/Mechanism;)Ljava/util/List; + public static fun hasOnlySystemFrames (Lio/sentry/SentryEvent;)Z +} + public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable { public fun (Landroid/content/Context;)V public fun close ()V diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java new file mode 100644 index 0000000000..0c79de4db0 --- /dev/null +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java @@ -0,0 +1,74 @@ +package io.sentry.android.core; + +import io.sentry.SentryEvent; +import io.sentry.SentryExceptionFactory; +import io.sentry.android.core.anr.AggregatedStackTrace; +import io.sentry.android.core.anr.AnrCulpritIdentifier; +import io.sentry.exception.ExceptionMechanismException; +import io.sentry.protocol.Mechanism; +import io.sentry.protocol.SentryException; +import io.sentry.protocol.SentryStackFrame; +import io.sentry.protocol.SentryStackTrace; +import java.util.List; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** Turns the culprit of an ANR profile into event exceptions, for fatal and non-fatal ANRs. */ +@ApiStatus.Internal +public final class AnrCulpritEventHelper { + + private final @NotNull SentryExceptionFactory exceptionFactory; + + public AnrCulpritEventHelper(final @NotNull SentryExceptionFactory exceptionFactory) { + this.exceptionFactory = exceptionFactory; + } + + /** + * Creates the exceptions for the culprit stack. The message is the class and method of the top + * frame. + * + * @return the exceptions, or {@code null} if the culprit stack is empty + */ + public @Nullable List createExceptions( + final @NotNull AggregatedStackTrace culprit, final @NotNull Mechanism mechanism) { + final @NotNull StackTraceElement[] stack = culprit.getStack(); + if (stack.length == 0) { + return null; + } + final @NotNull StackTraceElement top = stack[0]; + final @NotNull ApplicationNotResponding exception = + new ApplicationNotResponding(top.getClassName() + "." + top.getMethodName()); + exception.setStackTrace(stack); + + return exceptionFactory.getSentryExceptions( + new ExceptionMechanismException(mechanism, exception, null, false)); + } + + /** Returns {@code true} if the event has exceptions and none of their frames is an app frame. */ + public static boolean hasOnlySystemFrames(final @NotNull SentryEvent event) { + final @Nullable List exceptions = event.getExceptions(); + if (exceptions == null || exceptions.isEmpty()) { + return false; + } + + for (final @NotNull SentryException exception : exceptions) { + final @Nullable SentryStackTrace stacktrace = exception.getStacktrace(); + if (stacktrace != null) { + final @Nullable List frames = stacktrace.getFrames(); + if (frames != null && !frames.isEmpty()) { + for (final @NotNull SentryStackFrame frame : frames) { + if (frame.isInApp() != null && frame.isInApp()) { + return false; + } + final @Nullable String module = frame.getModule(); + if (module != null && !AnrCulpritIdentifier.isSystemFrame(module)) { + return false; + } + } + } + } + } + return true; + } +} diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java index a82a6de5db..ec29d032a4 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java @@ -48,7 +48,6 @@ import io.sentry.android.core.internal.util.CpuInfoUtils; import io.sentry.cache.PersistingOptionsObserver; import io.sentry.cache.PersistingScopeObserver; -import io.sentry.exception.ExceptionMechanismException; import io.sentry.hints.AbnormalExit; import io.sentry.hints.Backfillable; import io.sentry.hints.NativeCrashExit; @@ -64,7 +63,6 @@ import io.sentry.protocol.SdkVersion; import io.sentry.protocol.SentryException; import io.sentry.protocol.SentryId; -import io.sentry.protocol.SentryStackFrame; import io.sentry.protocol.SentryStackTrace; import io.sentry.protocol.SentryThread; import io.sentry.protocol.SentryTransaction; @@ -100,6 +98,7 @@ public final class ApplicationExitInfoEventProcessor private final @NotNull BuildInfoProvider buildInfoProvider; private final @NotNull SentryExceptionFactory sentryExceptionFactory; + private final @NotNull AnrCulpritEventHelper culpritEventHelper; private final @Nullable PersistingScopeObserver persistingScopeObserver; @@ -121,6 +120,7 @@ public ApplicationExitInfoEventProcessor( new SentryStackTraceFactory(this.options); sentryExceptionFactory = new SentryExceptionFactory(sentryStackTraceFactory); + culpritEventHelper = new AnrCulpritEventHelper(sentryExceptionFactory); } private @Nullable HintEnricher findEnricher(final @NotNull Object hint) { @@ -859,7 +859,7 @@ private void setDefaultAnrFingerprint( return; } - if (options.isEnableAnrFingerprinting() && hasOnlySystemFrames(event)) { + if (options.isEnableAnrFingerprinting() && AnrCulpritEventHelper.hasOnlySystemFrames(event)) { // If the stacktrace only contains system frames, we want to statically group these events // to avoid ANR noise due to {{ default }} stacktrace grouping event.setFingerprints( @@ -1002,28 +1002,17 @@ private void applyAnrProfile( // Capture profile chunk final @Nullable SentryId profilerId = captureAnrProfile(anrTimestamp, anrProfile, optionsSource); - final @NotNull StackTraceElement[] stack = culprit.getStack(); - - if (stack.length > 0) { - final StackTraceElement stackTraceElement = stack[0]; - final String message = - stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName(); - final ApplicationNotResponding exception = new ApplicationNotResponding(message); - exception.setStackTrace(stack); - - final Mechanism mechanism = new Mechanism(); - mechanism.setType("ANR"); - final ExceptionMechanismException error = - new ExceptionMechanismException(mechanism, exception, null, false); - - final @NotNull List sentryException = - sentryExceptionFactory.getSentryExceptions(error); + final Mechanism mechanism = new Mechanism(); + mechanism.setType("ANR"); + final @Nullable List sentryExceptions = + culpritEventHelper.createExceptions(culprit, mechanism); + if (sentryExceptions != null) { // Replace the original ANR exception with the profile-derived one, // as we assume the profiling culprit identification is more valuable // the event threads are kept as-is, so the original main thread stacktrace is still // available - event.setExceptions(sentryException); + event.setExceptions(sentryExceptions); if (profilerId != null) { event.getContexts().setProfile(new ProfileContext(profilerId)); @@ -1057,32 +1046,6 @@ private SentryId captureAnrProfile( } } - private boolean hasOnlySystemFrames(@NotNull SentryEvent event) { - final List exceptions = event.getExceptions(); - if (exceptions == null || exceptions.isEmpty()) { - return false; - } - - for (final SentryException exception : exceptions) { - final SentryStackTrace stacktrace = exception.getStacktrace(); - if (stacktrace != null) { - final List frames = stacktrace.getFrames(); - if (frames != null && !frames.isEmpty()) { - for (final SentryStackFrame frame : frames) { - if (frame.isInApp() != null && frame.isInApp()) { - return false; - } - final String module = frame.getModule(); - if (module != null && !AnrCulpritIdentifier.isSystemFrame(module)) { - return false; - } - } - } - } - } - return true; - } - /** * Creates debug metadata for an ANR profile chunk using the build metadata selected for the ANR * event. diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt new file mode 100644 index 0000000000..09014495db --- /dev/null +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt @@ -0,0 +1,85 @@ +package io.sentry.android.core + +import com.google.common.truth.Truth.assertThat +import io.sentry.SentryEvent +import io.sentry.SentryExceptionFactory +import io.sentry.SentryOptions +import io.sentry.SentryStackTraceFactory +import io.sentry.android.core.anr.AggregatedStackTrace +import io.sentry.protocol.Mechanism +import io.sentry.protocol.SentryException +import io.sentry.protocol.SentryStackFrame +import io.sentry.protocol.SentryStackTrace +import kotlin.test.Test + +class AnrCulpritEventHelperTest { + + private val helper = + AnrCulpritEventHelper(SentryExceptionFactory(SentryStackTraceFactory(SentryOptions()))) + + @Test + fun `creates exception with class and method of the top frame as message`() { + val stack = + arrayOf( + StackTraceElement("com.example.Foo", "bar", "Foo.java", 12), + StackTraceElement("android.os.Looper", "loop", "Looper.java", 200), + ) + val culprit = AggregatedStackTrace(stack, 0, stack.size - 1, 1000L, 0.5f) + + val exceptions = helper.createExceptions(culprit, Mechanism().apply { type = "ANR" }) + + assertThat(exceptions).isNotNull() + assertThat(exceptions!!).hasSize(1) + val exception = exceptions[0] + assertThat(exception.type).isEqualTo("ApplicationNotResponding") + assertThat(exception.value).isEqualTo("com.example.Foo.bar") + assertThat(exception.mechanism!!.type).isEqualTo("ANR") + assertThat(exception.stacktrace!!.frames!!.map { it.function }).containsExactly("loop", "bar") + } + + @Test + fun `returns null for an empty culprit stack`() { + val culprit = AggregatedStackTrace(emptyArray(), 0, -1, 1000L, 0f) + + assertThat(helper.createExceptions(culprit, Mechanism())).isNull() + } + + @Test + fun `hasOnlySystemFrames is true for java and android frames only`() { + val event = eventWithFrames(frame("java.lang.Object"), frame("android.os.Looper")) + + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(event)).isTrue() + } + + @Test + fun `hasOnlySystemFrames is false if one frame is in app`() { + val event = + eventWithFrames(frame("android.os.Looper"), frame("android.os.Handler", inApp = true)) + + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(event)).isFalse() + } + + @Test + fun `hasOnlySystemFrames is false if one frame is not a system frame`() { + val event = eventWithFrames(frame("android.os.Looper"), frame("com.example.Foo")) + + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(event)).isFalse() + } + + @Test + fun `hasOnlySystemFrames is false if the event has no exceptions`() { + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(SentryEvent())).isFalse() + } + + private fun frame(module: String, inApp: Boolean? = null) = + SentryStackFrame().apply { + this.module = module + this.isInApp = inApp + } + + private fun eventWithFrames(vararg frames: SentryStackFrame) = + SentryEvent().apply { + exceptions = + listOf(SentryException().apply { stacktrace = SentryStackTrace(frames.toList()) }) + } +} From 2887ad2d3dcb3121c73e36ed2fa9286c760347aa Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Wed, 11 Nov 2026 15:47:33 +0100 Subject: [PATCH 2/5] ref(android): Trim a redundant Javadoc sentence in AnrCulpritEventHelper --- .../java/io/sentry/android/core/AnrCulpritEventHelper.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java index 0c79de4db0..2a01fd3f4b 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java @@ -25,8 +25,7 @@ public AnrCulpritEventHelper(final @NotNull SentryExceptionFactory exceptionFact } /** - * Creates the exceptions for the culprit stack. The message is the class and method of the top - * frame. + * The exception message is the class and method of the top frame of the culprit stack. * * @return the exceptions, or {@code null} if the culprit stack is empty */ From a6bda161e917bfd3b872a527af28a9b35ffc5a91 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 12 Nov 2026 10:50:01 +0100 Subject: [PATCH 3/5] ref(android): Rename AnrCulpritEventHelper methods and take exceptions directly --- .../api/sentry-android-core.api | 4 +-- .../android/core/AnrCulpritEventHelper.java | 15 +++++---- .../ApplicationExitInfoEventProcessor.java | 5 +-- .../android/core/AnrCulpritEventHelperTest.kt | 31 +++++++++---------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index a896744f40..d1c9d1e169 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -149,8 +149,8 @@ public final class io/sentry/android/core/AndroidSocketTagger : io/sentry/ISocke public final class io/sentry/android/core/AnrCulpritEventHelper { public fun (Lio/sentry/SentryExceptionFactory;)V - public fun createExceptions (Lio/sentry/android/core/anr/AggregatedStackTrace;Lio/sentry/protocol/Mechanism;)Ljava/util/List; - public static fun hasOnlySystemFrames (Lio/sentry/SentryEvent;)Z + public static fun hasOnlySystemFrames (Ljava/util/List;)Z + public fun toSentryExceptions (Lio/sentry/android/core/anr/AggregatedStackTrace;Lio/sentry/protocol/Mechanism;)Ljava/util/List; } public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable { diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java index 2a01fd3f4b..78f7203187 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java @@ -1,6 +1,5 @@ package io.sentry.android.core; -import io.sentry.SentryEvent; import io.sentry.SentryExceptionFactory; import io.sentry.android.core.anr.AggregatedStackTrace; import io.sentry.android.core.anr.AnrCulpritIdentifier; @@ -14,7 +13,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** Turns the culprit of an ANR profile into event exceptions, for fatal and non-fatal ANRs. */ @ApiStatus.Internal public final class AnrCulpritEventHelper { @@ -29,7 +27,7 @@ public AnrCulpritEventHelper(final @NotNull SentryExceptionFactory exceptionFact * * @return the exceptions, or {@code null} if the culprit stack is empty */ - public @Nullable List createExceptions( + public @Nullable List toSentryExceptions( final @NotNull AggregatedStackTrace culprit, final @NotNull Mechanism mechanism) { final @NotNull StackTraceElement[] stack = culprit.getStack(); if (stack.length == 0) { @@ -44,9 +42,14 @@ public AnrCulpritEventHelper(final @NotNull SentryExceptionFactory exceptionFact new ExceptionMechanismException(mechanism, exception, null, false)); } - /** Returns {@code true} if the event has exceptions and none of their frames is an app frame. */ - public static boolean hasOnlySystemFrames(final @NotNull SentryEvent event) { - final @Nullable List exceptions = event.getExceptions(); + /** + * A frame is a system frame if it is not in app and its module is a Java, Kotlin, or Android + * framework package, see {@link AnrCulpritIdentifier#isSystemFrame(String)}. + * + * @return {@code true} if there is at least one exception and none of their frames is an app + * frame + */ + public static boolean hasOnlySystemFrames(final @Nullable List exceptions) { if (exceptions == null || exceptions.isEmpty()) { return false; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java index ec29d032a4..876ca0a0a7 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java @@ -859,7 +859,8 @@ private void setDefaultAnrFingerprint( return; } - if (options.isEnableAnrFingerprinting() && AnrCulpritEventHelper.hasOnlySystemFrames(event)) { + if (options.isEnableAnrFingerprinting() + && AnrCulpritEventHelper.hasOnlySystemFrames(event.getExceptions())) { // If the stacktrace only contains system frames, we want to statically group these events // to avoid ANR noise due to {{ default }} stacktrace grouping event.setFingerprints( @@ -1005,7 +1006,7 @@ private void applyAnrProfile( final Mechanism mechanism = new Mechanism(); mechanism.setType("ANR"); final @Nullable List sentryExceptions = - culpritEventHelper.createExceptions(culprit, mechanism); + culpritEventHelper.toSentryExceptions(culprit, mechanism); if (sentryExceptions != null) { // Replace the original ANR exception with the profile-derived one, diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt index 09014495db..b7bb7b99f6 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt @@ -1,7 +1,6 @@ package io.sentry.android.core import com.google.common.truth.Truth.assertThat -import io.sentry.SentryEvent import io.sentry.SentryExceptionFactory import io.sentry.SentryOptions import io.sentry.SentryStackTraceFactory @@ -26,7 +25,7 @@ class AnrCulpritEventHelperTest { ) val culprit = AggregatedStackTrace(stack, 0, stack.size - 1, 1000L, 0.5f) - val exceptions = helper.createExceptions(culprit, Mechanism().apply { type = "ANR" }) + val exceptions = helper.toSentryExceptions(culprit, Mechanism().apply { type = "ANR" }) assertThat(exceptions).isNotNull() assertThat(exceptions!!).hasSize(1) @@ -41,34 +40,35 @@ class AnrCulpritEventHelperTest { fun `returns null for an empty culprit stack`() { val culprit = AggregatedStackTrace(emptyArray(), 0, -1, 1000L, 0f) - assertThat(helper.createExceptions(culprit, Mechanism())).isNull() + assertThat(helper.toSentryExceptions(culprit, Mechanism())).isNull() } @Test fun `hasOnlySystemFrames is true for java and android frames only`() { - val event = eventWithFrames(frame("java.lang.Object"), frame("android.os.Looper")) + val exceptions = exceptionsWithFrames(frame("java.lang.Object"), frame("android.os.Looper")) - assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(event)).isTrue() + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(exceptions)).isTrue() } @Test fun `hasOnlySystemFrames is false if one frame is in app`() { - val event = - eventWithFrames(frame("android.os.Looper"), frame("android.os.Handler", inApp = true)) + val exceptions = + exceptionsWithFrames(frame("android.os.Looper"), frame("android.os.Handler", inApp = true)) - assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(event)).isFalse() + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(exceptions)).isFalse() } @Test fun `hasOnlySystemFrames is false if one frame is not a system frame`() { - val event = eventWithFrames(frame("android.os.Looper"), frame("com.example.Foo")) + val exceptions = exceptionsWithFrames(frame("android.os.Looper"), frame("com.example.Foo")) - assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(event)).isFalse() + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(exceptions)).isFalse() } @Test - fun `hasOnlySystemFrames is false if the event has no exceptions`() { - assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(SentryEvent())).isFalse() + fun `hasOnlySystemFrames is false without exceptions`() { + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(null)).isFalse() + assertThat(AnrCulpritEventHelper.hasOnlySystemFrames(emptyList())).isFalse() } private fun frame(module: String, inApp: Boolean? = null) = @@ -77,9 +77,6 @@ class AnrCulpritEventHelperTest { this.isInApp = inApp } - private fun eventWithFrames(vararg frames: SentryStackFrame) = - SentryEvent().apply { - exceptions = - listOf(SentryException().apply { stacktrace = SentryStackTrace(frames.toList()) }) - } + private fun exceptionsWithFrames(vararg frames: SentryStackFrame) = + listOf(SentryException().apply { stacktrace = SentryStackTrace(frames.toList()) }) } From 7effbd9cd3eee0682f7331ef31a4f015c5aec54b Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 12 Nov 2026 11:44:30 +0100 Subject: [PATCH 4/5] ref(android): Move AnrCulpritEventHelper into the anr package --- sentry-android-core/api/sentry-android-core.api | 17 +++++++++++------ .../core/ApplicationExitInfoEventProcessor.java | 1 + .../android/core/ApplicationNotResponding.java | 6 ++++-- .../core/{ => anr}/AnrCulpritEventHelper.java | 5 ++--- .../core/{ => anr}/AnrCulpritEventHelperTest.kt | 4 ++-- 5 files changed, 20 insertions(+), 13 deletions(-) rename sentry-android-core/src/main/java/io/sentry/android/core/{ => anr}/AnrCulpritEventHelper.java (94%) rename sentry-android-core/src/test/java/io/sentry/android/core/{ => anr}/AnrCulpritEventHelperTest.kt (96%) diff --git a/sentry-android-core/api/sentry-android-core.api b/sentry-android-core/api/sentry-android-core.api index d1c9d1e169..5ab0634a49 100644 --- a/sentry-android-core/api/sentry-android-core.api +++ b/sentry-android-core/api/sentry-android-core.api @@ -147,12 +147,6 @@ public final class io/sentry/android/core/AndroidSocketTagger : io/sentry/ISocke public fun untagSockets ()V } -public final class io/sentry/android/core/AnrCulpritEventHelper { - public fun (Lio/sentry/SentryExceptionFactory;)V - public static fun hasOnlySystemFrames (Ljava/util/List;)Z - public fun toSentryExceptions (Lio/sentry/android/core/anr/AggregatedStackTrace;Lio/sentry/protocol/Mechanism;)Ljava/util/List; -} - public final class io/sentry/android/core/AnrIntegration : io/sentry/Integration, java/io/Closeable { public fun (Landroid/content/Context;)V public fun close ()V @@ -250,6 +244,11 @@ public final class io/sentry/android/core/ApplicationExitInfoEventProcessor : io public fun process (Lio/sentry/protocol/SentryTransaction;Lio/sentry/Hint;)Lio/sentry/protocol/SentryTransaction; } +public final class io/sentry/android/core/ApplicationNotResponding : java/lang/RuntimeException { + public fun (Ljava/lang/String;)V + public fun getThread ()Ljava/lang/Thread; +} + public final class io/sentry/android/core/BuildConfig { public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z @@ -722,6 +721,12 @@ public final class io/sentry/android/core/anr/AggregatedStackTrace { public fun getStack ()[Ljava/lang/StackTraceElement; } +public final class io/sentry/android/core/anr/AnrCulpritEventHelper { + public fun (Lio/sentry/SentryExceptionFactory;)V + public static fun hasOnlySystemFrames (Ljava/util/List;)Z + public fun toSentryExceptions (Lio/sentry/android/core/anr/AggregatedStackTrace;Lio/sentry/protocol/Mechanism;)Ljava/util/List; +} + public final class io/sentry/android/core/anr/AnrCulpritIdentifier { public fun ()V public static fun identify (Ljava/util/List;)Lio/sentry/android/core/anr/AggregatedStackTrace; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java index 876ca0a0a7..b96fca8b0e 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationExitInfoEventProcessor.java @@ -40,6 +40,7 @@ import io.sentry.SentryStackTraceFactory; import io.sentry.SpanContext; import io.sentry.android.core.anr.AggregatedStackTrace; +import io.sentry.android.core.anr.AnrCulpritEventHelper; import io.sentry.android.core.anr.AnrCulpritIdentifier; import io.sentry.android.core.anr.AnrProfile; import io.sentry.android.core.anr.AnrProfileManager; diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java index 120f2e0bef..70b9bc8240 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java @@ -3,6 +3,7 @@ package io.sentry.android.core; import io.sentry.util.Objects; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -14,14 +15,15 @@ * really the cause of the exception. Each "Caused by" is the stack trace of a running thread. Note * that the main thread always comes first. */ -final class ApplicationNotResponding extends RuntimeException { +@ApiStatus.Internal +public final class ApplicationNotResponding extends RuntimeException { private static final long serialVersionUID = 252541144579117016L; // temp fix for JDK 21: Never serialized. @SuppressWarnings("serial") private final @Nullable Thread thread; - ApplicationNotResponding(final @Nullable String message) { + public ApplicationNotResponding(final @Nullable String message) { super(message); this.thread = null; } diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java b/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrCulpritEventHelper.java similarity index 94% rename from sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java rename to sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrCulpritEventHelper.java index 78f7203187..b9671bb883 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AnrCulpritEventHelper.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrCulpritEventHelper.java @@ -1,8 +1,7 @@ -package io.sentry.android.core; +package io.sentry.android.core.anr; import io.sentry.SentryExceptionFactory; -import io.sentry.android.core.anr.AggregatedStackTrace; -import io.sentry.android.core.anr.AnrCulpritIdentifier; +import io.sentry.android.core.ApplicationNotResponding; import io.sentry.exception.ExceptionMechanismException; import io.sentry.protocol.Mechanism; import io.sentry.protocol.SentryException; diff --git a/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt b/sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrCulpritEventHelperTest.kt similarity index 96% rename from sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt rename to sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrCulpritEventHelperTest.kt index b7bb7b99f6..c2d82ba8e3 100644 --- a/sentry-android-core/src/test/java/io/sentry/android/core/AnrCulpritEventHelperTest.kt +++ b/sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrCulpritEventHelperTest.kt @@ -1,10 +1,9 @@ -package io.sentry.android.core +package io.sentry.android.core.anr import com.google.common.truth.Truth.assertThat import io.sentry.SentryExceptionFactory import io.sentry.SentryOptions import io.sentry.SentryStackTraceFactory -import io.sentry.android.core.anr.AggregatedStackTrace import io.sentry.protocol.Mechanism import io.sentry.protocol.SentryException import io.sentry.protocol.SentryStackFrame @@ -31,6 +30,7 @@ class AnrCulpritEventHelperTest { assertThat(exceptions!!).hasSize(1) val exception = exceptions[0] assertThat(exception.type).isEqualTo("ApplicationNotResponding") + assertThat(exception.module).isEqualTo("io.sentry.android.core") assertThat(exception.value).isEqualTo("com.example.Foo.bar") assertThat(exception.mechanism!!.type).isEqualTo("ANR") assertThat(exception.stacktrace!!.frames!!.map { it.function }).containsExactly("loop", "bar") From 9c363b4130bd4f972304349c4747fec6503fd970 Mon Sep 17 00:00:00 2001 From: Markus Hintersteiner Date: Thu, 12 Nov 2026 12:07:26 +0100 Subject: [PATCH 5/5] license(android): Complete the ANR-WatchDog attribution for ApplicationNotResponding --- THIRD_PARTY_NOTICES.md | 2 +- .../io/sentry/android/core/ApplicationNotResponding.java | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index b4c58711dd..62d80f721b 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -455,7 +455,7 @@ limitations under the License. ### Scope -The Sentry Java SDK includes an adapted version of the ANR-WatchDog library for Application Not Responding (ANR) detection on Android. The code resides in `io.sentry.android.core.ANRWatchDog`. +The Sentry Java SDK includes an adapted version of the ANR-WatchDog library for Application Not Responding (ANR) detection on Android. The code resides in `io.sentry.android.core.ANRWatchDog` and `io.sentry.android.core.ApplicationNotResponding` (adapted from `ANRError`). ``` MIT License diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java index 70b9bc8240..18279fbeaf 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/ApplicationNotResponding.java @@ -1,5 +1,8 @@ +// Adapted from ANR-WatchDog. +// Copyright (c) 2016 Salomon BRYS. +// Licensed under the MIT License. // https://github.com/SalomonBrys/ANR-WatchDog/blob/1969075f75f5980e9000eaffbaa13b0daf282dcb/anr-watchdog/src/main/java/com/github/anrwatchdog/ANRError.java -// Based on the class above. The API unnecessary here was removed. +// The API unnecessary here was removed. package io.sentry.android.core; import io.sentry.util.Objects;