Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 5 additions & 2 deletions src/Sentry.Unity.Android/SentryJava.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<AndroidJavaObject>("getLogs"))
{
logsOptions.Call("setEnabled", options.EnableLogs);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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());

Expand Down
30 changes: 30 additions & 0 deletions src/Sentry.Unity/SentryUnityOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,36 @@ public sealed class SentryUnityOptions : SentryOptions
/// </remarks>
public NativeInitializationType AndroidNativeInitializationType { get; set; } = NativeInitializationType.Runtime;

/// <summary>
/// Enables ANR detection on Android through the native (sentry-java) SDK.
/// On API ≥ 30 this uses Android's <c>ApplicationExitInfo</c> to report ANRs detected by the OS
/// in prior runs (ANR v2). On API &lt; 30, sentry-java falls back to its in-process watchdog.
/// The Unity SDK's C# ANR watchdog continues to run on all API levels.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public bool AndroidNativeAnrEnabled { get; set; } = false;

/// <summary>
/// When <see cref="AndroidNativeAnrEnabled"/> is enabled, controls whether sentry-java reports historical ANRs
/// recorded by the OS (<c>ApplicationExitInfo</c>) from prior runs. Has no effect when
/// <see cref="AndroidNativeAnrEnabled"/> is <c>false</c>.
/// </summary>
/// <remarks>
/// Runtime-only. There is no <c>AndroidManifest</c> meta-data tag for this option, so it is only
/// applied when <see cref="AndroidNativeInitializationType"/> is <see cref="NativeInitializationType.Runtime"/>.
/// </remarks>
public bool AndroidReportHistoricalAnrs { get; set; } = true;

/// <summary>
/// When <see cref="AndroidNativeAnrEnabled"/> is enabled, controls whether sentry-java attaches a thread dump
/// to ANR events. Has no effect when <see cref="AndroidNativeAnrEnabled"/> is <c>false</c>.
/// </summary>
public bool AndroidAttachAnrThreadDump { get; set; } = true;

/// <summary>
/// Whether the SDK should add the NDK integration for Android
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,45 @@ public void ModifyManifest_UnityOptions_AndroidNativeSupportEnabled_InitTypeBuil
StringAssert.Contains($"<meta-data android:name=\"io.sentry.dsn\" android:value=\"{_fixture.SentryUnityOptions.Dsn}\" />", 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("<meta-data android:name=\"io.sentry.anr.enable\" android:value=\"True\" />", manifest);
StringAssert.Contains("<meta-data android:name=\"io.sentry.enable-scope-persistence\" android:value=\"True\" />", manifest);
StringAssert.Contains("<meta-data android:name=\"io.sentry.anr.attach-thread-dumps\" android:value=\"True\" />", 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("<meta-data android:name=\"io.sentry.anr.enable\" android:value=\"False\" />", manifest);
StringAssert.Contains("<meta-data android:name=\"io.sentry.enable-scope-persistence\" android:value=\"False\" />", manifest);
StringAssert.Contains("<meta-data android:name=\"io.sentry.anr.attach-thread-dumps\" android:value=\"False\" />", 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("<meta-data android:name=\"io.sentry.anr.enable\" android:value=\"True\" />", manifest);
StringAssert.Contains("<meta-data android:name=\"io.sentry.anr.attach-thread-dumps\" android:value=\"False\" />", manifest);
}

[Test]
public void ModifyManifest_ManifestHasDsn()
{
Expand Down
Loading