diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa74a63d..443d4d446 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Added `AndroidNativeAnrEnabled` (default `false`) to enable ANR detection through the native (sentry-java) SDK, monitoring the Android UI (Looper) main thread. On API ≥ 30 this uses [ANR v2](https://docs.sentry.io/platforms/android/configuration/app-not-respond/) via `ApplicationExitInfo` to report OS-detected ANRs from prior runs; on API < 30 it falls back to an in-process watchdog. Complementary to the Unity SDK's C# watchdog, which monitors the Unity player loop. ([#2671](https://github.com/getsentry/sentry-unity/pull/2671)) + ### Dependencies - Bump Cocoa SDK from v9.12.0 to v9.12.1 ([#2670](https://github.com/getsentry/sentry-unity/pull/2670)) diff --git a/src/Sentry.Unity.Android/SentryJava.cs b/src/Sentry.Unity.Android/SentryJava.cs index b2435928b..77dffedfe 100644 --- a/src/Sentry.Unity.Android/SentryJava.cs +++ b/src/Sentry.Unity.Android/SentryJava.cs @@ -140,6 +140,11 @@ public void Init(SentryUnityOptions options) androidOptions.Call("setEnableScopeSync", options.NdkScopeSyncEnabled); androidOptions.Call("setNativeSdkName", "sentry.native.android.unity"); + androidOptions.Call("setAnrEnabled", options.AndroidNativeAnrEnabled); + androidOptions.Call("setEnableScopePersistence", options.AndroidNativeAnrEnabled); + androidOptions.Call("setReportHistoricalAnrs", options.AndroidNativeAnrEnabled && options.AndroidReportHistoricalAnrs); + androidOptions.Call("setAttachAnrThreadDump", options.AndroidNativeAnrEnabled && options.AndroidAttachAnrThreadDump); + using (var logsOptions = androidOptions.Call("getLogs")) { logsOptions.Call("setEnabled", options.EnableLogs); @@ -158,8 +163,6 @@ public void Init(SentryUnityOptions options) androidOptions.Call("setAttachScreenshot", false); androidOptions.Call("setEnableAutoSessionTracking", false); androidOptions.Call("setEnableActivityLifecycleBreadcrumbs", false); - androidOptions.Call("setAnrEnabled", false); - androidOptions.Call("setEnableScopePersistence", false); // Disable user interaction tracking to prevent conflicts with VR platforms (e.g., Oculus InputHooks) androidOptions.Call("setEnableUserInteractionBreadcrumbs", false); androidOptions.Call("setEnableUserInteractionTracing", false); diff --git a/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs b/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs index 1c90cacb6..22a806f9f 100644 --- a/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs +++ b/src/Sentry.Unity.Editor/Android/AndroidManifestConfiguration.cs @@ -218,8 +218,9 @@ internal void ModifyManifest(string basePath) androidManifest.SetAutoTraceIdGeneration(false); androidManifest.SetAutoSessionTracking(false); androidManifest.SetAutoAppLifecycleBreadcrumbs(false); - androidManifest.SetAnr(false); - androidManifest.SetPersistentScopeObserver(false); + androidManifest.SetAnr(_options.AndroidNativeAnrEnabled); + androidManifest.SetPersistentScopeObserver(_options.AndroidNativeAnrEnabled); + androidManifest.SetAttachAnrThreadDump(_options.AndroidNativeAnrEnabled && _options.AndroidAttachAnrThreadDump); // Disable user interaction tracking to prevent conflicts with VR platforms (e.g., Oculus InputHooks) androidManifest.SetEnableUserInteractionBreadcrumbs(false); androidManifest.SetEnableUserInteractionTracing(false); @@ -495,6 +496,9 @@ internal void SetAnr(bool enableAnr) internal void SetPersistentScopeObserver(bool enableScopePersistence) => SetMetaData($"{SentryPrefix}.enable-scope-persistence", enableScopePersistence.ToString()); + internal void SetAttachAnrThreadDump(bool attachAnrThreadDump) + => SetMetaData($"{SentryPrefix}.anr.attach-thread-dumps", attachAnrThreadDump.ToString()); + internal void SetNdkEnabled(bool enableNdk) => SetMetaData($"{SentryPrefix}.ndk.enable", enableNdk.ToString()); diff --git a/src/Sentry.Unity/SentryUnityOptions.cs b/src/Sentry.Unity/SentryUnityOptions.cs index 76235352a..c57dc28fa 100644 --- a/src/Sentry.Unity/SentryUnityOptions.cs +++ b/src/Sentry.Unity/SentryUnityOptions.cs @@ -214,6 +214,36 @@ public sealed class SentryUnityOptions : SentryOptions /// public NativeInitializationType AndroidNativeInitializationType { get; set; } = NativeInitializationType.Runtime; + /// + /// Enables ANR detection on Android through the native (sentry-java) SDK. + /// On API ≥ 30 this uses Android's ApplicationExitInfo to report ANRs detected by the OS + /// in prior runs (ANR v2). On API < 30, sentry-java falls back to its in-process watchdog. + /// The Unity SDK's C# ANR watchdog continues to run on all API levels. + /// + /// + /// The Java and C# watchdogs observe different threads and are complementary: the Java watchdog + /// monitors the Android UI (Looper) main thread, while the C# watchdog monitors the Unity engine + /// main thread (the player loop). A hang that blocks both threads can produce one event from each. + /// + public bool AndroidNativeAnrEnabled { get; set; } = false; + + /// + /// When is enabled, controls whether sentry-java reports historical ANRs + /// recorded by the OS (ApplicationExitInfo) from prior runs. Has no effect when + /// is false. + /// + /// + /// Runtime-only. There is no AndroidManifest meta-data tag for this option, so it is only + /// applied when is . + /// + public bool AndroidReportHistoricalAnrs { get; set; } = true; + + /// + /// When is enabled, controls whether sentry-java attaches a thread dump + /// to ANR events. Has no effect when is false. + /// + public bool AndroidAttachAnrThreadDump { get; set; } = true; + /// /// Whether the SDK should add the NDK integration for Android /// diff --git a/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs b/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs index e8cb9a54c..794d372f9 100644 --- a/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs +++ b/test/Sentry.Unity.Editor.Tests/Android/AndroidManifestConfigurationTests.cs @@ -150,6 +150,45 @@ public void ModifyManifest_UnityOptions_AndroidNativeSupportEnabled_InitTypeBuil StringAssert.Contains($"", manifest); } + [Test] + public void ModifyManifest_AndroidNativeAnrEnabled_True_WritesAnrMetadataEnabled() + { + _fixture.SentryUnityOptions!.AndroidNativeAnrEnabled = true; + var sut = _fixture.GetSut(); + + var manifest = WithAndroidManifest(basePath => sut.ModifyManifest(basePath)); + + StringAssert.Contains("", manifest); + StringAssert.Contains("", manifest); + StringAssert.Contains("", manifest); + } + + [Test] + public void ModifyManifest_AndroidNativeAnrEnabled_False_WritesAnrMetadataDisabled() + { + _fixture.SentryUnityOptions!.AndroidNativeAnrEnabled = false; + var sut = _fixture.GetSut(); + + var manifest = WithAndroidManifest(basePath => sut.ModifyManifest(basePath)); + + StringAssert.Contains("", manifest); + StringAssert.Contains("", manifest); + StringAssert.Contains("", manifest); + } + + [Test] + public void ModifyManifest_AndroidAttachAnrThreadDumpFalse_OverridesUmbrella() + { + _fixture.SentryUnityOptions!.AndroidNativeAnrEnabled = true; + _fixture.SentryUnityOptions!.AndroidAttachAnrThreadDump = false; + var sut = _fixture.GetSut(); + + var manifest = WithAndroidManifest(basePath => sut.ModifyManifest(basePath)); + + StringAssert.Contains("", manifest); + StringAssert.Contains("", manifest); + } + [Test] public void ModifyManifest_ManifestHasDsn() {