From 6c4579a111e9a62581f4689d7d7a3f1d35b6d01e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 19:43:33 +0000 Subject: [PATCH 1/5] Initial plan From 4f3b8c127f9577e03d6de28d17e7fe8b88900ce5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:16:24 +0000 Subject: [PATCH 2/5] Make AndroidEnvironmentInternal.UnhandledException public to fix MethodAccessException Co-authored-by: Marek Habersack Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- .../AndroidEnvironmentInternal.cs | 10 +++- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 55 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs index 198e6c92423..723dbdfe6d9 100644 --- a/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs +++ b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs @@ -1,12 +1,18 @@ using System; +using System.ComponentModel; +using System.Diagnostics; namespace Android.Runtime { - internal static class AndroidEnvironmentInternal + [DebuggerBrowsable (DebuggerBrowsableState.Never)] + [EditorBrowsable (EditorBrowsableState.Never)] + public static class AndroidEnvironmentInternal { internal static Action? UnhandledExceptionHandler; - internal static void UnhandledException (Exception e) + [DebuggerBrowsable (DebuggerBrowsableState.Never)] + [EditorBrowsable (EditorBrowsableState.Never)] + public static void UnhandledException (Exception e) { if (UnhandledExceptionHandler == null) { return; diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index e26bce1dc50..e7dbce40a99 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -2153,5 +2153,60 @@ public void Plugin_Maui_Audio ([Values] AndroidRuntime runtime) const string className = "Lcrc64467b05f37239e7a6/StreamMediaDataSource;"; Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, AndroidSdkPath), $"`{dexFile}` should include `{className}`!"); } + + [Test] + public void AndroidEnvironmentInternalIsPublic ([Values] AndroidRuntime runtime) + { + const bool isRelease = true; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + + // NativeAOT does not use marshal methods in the same way + if (runtime == AndroidRuntime.NativeAOT) { + Assert.Ignore ("NativeAOT does not use marshal methods"); + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = isRelease, + EnableMarshalMethods = true, + }; + proj.SetRuntime (runtime); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); + + string monoAndroidRuntimePath = Path.Combine ( + Root, + builder.ProjectDirectory, + proj.IntermediateOutputPath, + "android-arm64", + "linked", + "Mono.Android.Runtime.dll" + ); + + if (!File.Exists (monoAndroidRuntimePath)) { + Assert.Ignore ($"Mono.Android.Runtime.dll not found at expected path: {monoAndroidRuntimePath}"); + return; + } + + using var assembly = AssemblyDefinition.ReadAssembly (monoAndroidRuntimePath); + const string expectedTypeName = "Android.Runtime.AndroidEnvironmentInternal"; + + TypeDefinition foundType = null; + foreach (var module in assembly.Modules) { + foreach (var type in module.Types) { + if (string.Equals (type.FullName, expectedTypeName, StringComparison.Ordinal)) { + foundType = type; + break; + } + } + if (foundType != null) break; + } + + Assert.IsNotNull (foundType, $"Type '{expectedTypeName}' should exist in '{monoAndroidRuntimePath}'"); + Assert.IsTrue (foundType.IsPublic, $"Type '{expectedTypeName}' should be public"); + } } } From 23936e13c0d37e61e5a3e7b382efeb353a337510 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 20:17:08 +0000 Subject: [PATCH 3/5] Update test to also verify UnhandledException method is public Co-authored-by: Marek Habersack Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index e7dbce40a99..6b97f40942d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -2193,20 +2193,20 @@ public void AndroidEnvironmentInternalIsPublic ([Values] AndroidRuntime runtime) using var assembly = AssemblyDefinition.ReadAssembly (monoAndroidRuntimePath); const string expectedTypeName = "Android.Runtime.AndroidEnvironmentInternal"; + const string expectedMethodName = "UnhandledException"; - TypeDefinition foundType = null; - foreach (var module in assembly.Modules) { - foreach (var type in module.Types) { - if (string.Equals (type.FullName, expectedTypeName, StringComparison.Ordinal)) { - foundType = type; - break; - } - } - if (foundType != null) break; - } + var foundType = assembly.Modules + .SelectMany (m => m.Types) + .FirstOrDefault (t => string.Equals (t.FullName, expectedTypeName, StringComparison.Ordinal)); Assert.IsNotNull (foundType, $"Type '{expectedTypeName}' should exist in '{monoAndroidRuntimePath}'"); Assert.IsTrue (foundType.IsPublic, $"Type '{expectedTypeName}' should be public"); + + var unhandledExceptionMethod = foundType.Methods + .FirstOrDefault (m => string.Equals (m.Name, expectedMethodName, StringComparison.Ordinal)); + + Assert.IsNotNull (unhandledExceptionMethod, $"Method '{expectedMethodName}' should exist in type '{expectedTypeName}'"); + Assert.IsTrue (unhandledExceptionMethod.IsPublic, $"Method '{expectedMethodName}' should be public"); } } } From afaba98d2c3cc25d93aaf054d66755191c300744 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 21:50:20 +0000 Subject: [PATCH 4/5] Match test to grendello's version from PR #10607 Co-authored-by: Marek Habersack Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs index 6b97f40942d..da233a70a47 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs @@ -2155,17 +2155,21 @@ public void Plugin_Maui_Audio ([Values] AndroidRuntime runtime) } [Test] - public void AndroidEnvironmentInternalIsPublic ([Values] AndroidRuntime runtime) + public void MarshalMethodsUnhandledExceptionRuntimeFixUpWorks ([Values] AndroidRuntime runtime) { const bool isRelease = true; if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } - // NativeAOT does not use marshal methods in the same way - if (runtime == AndroidRuntime.NativeAOT) { - Assert.Ignore ("NativeAOT does not use marshal methods"); - return; + switch (runtime) { + case AndroidRuntime.NativeAOT: + Assert.Ignore ("NativeAOT does not support marshal methods"); + break; + + case AndroidRuntime.CoreCLR: + Assert.Ignore ("CoreCLR currently doesn't work due to a bug in Mono.Cecil"); + break; } var proj = new XamarinAndroidApplicationProject { @@ -2173,7 +2177,6 @@ public void AndroidEnvironmentInternalIsPublic ([Values] AndroidRuntime runtime) EnableMarshalMethods = true, }; proj.SetRuntime (runtime); - using var builder = CreateApkBuilder (); Assert.IsTrue (builder.Build (proj), "Build should have succeeded."); @@ -2185,28 +2188,36 @@ public void AndroidEnvironmentInternalIsPublic ([Values] AndroidRuntime runtime) "linked", "Mono.Android.Runtime.dll" ); + FileAssert.Exists (monoAndroidRuntimePath); - if (!File.Exists (monoAndroidRuntimePath)) { - Assert.Ignore ($"Mono.Android.Runtime.dll not found at expected path: {monoAndroidRuntimePath}"); - return; - } - - using var assembly = AssemblyDefinition.ReadAssembly (monoAndroidRuntimePath); - const string expectedTypeName = "Android.Runtime.AndroidEnvironmentInternal"; - const string expectedMethodName = "UnhandledException"; + using var asm = AssemblyDefinition.ReadAssembly (monoAndroidRuntimePath); + const string TypeName = "Android.Runtime.AndroidEnvironmentInternal"; + TypeDefinition? type = null; - var foundType = assembly.Modules - .SelectMany (m => m.Types) - .FirstOrDefault (t => string.Equals (t.FullName, expectedTypeName, StringComparison.Ordinal)); + foreach (ModuleDefinition module in asm.Modules) { + foreach (TypeDefinition t in module.Types) { + if (t.FullName.Equals (TypeName, StringComparison.Ordinal)) { + type = t; + break; + } + } + } - Assert.IsNotNull (foundType, $"Type '{expectedTypeName}' should exist in '{monoAndroidRuntimePath}'"); - Assert.IsTrue (foundType.IsPublic, $"Type '{expectedTypeName}' should be public"); + Assert.NotNull (type, $"Failed to find the '{TypeName}' type in '{monoAndroidRuntimePath}'"); + Assert.IsTrue (type.IsPublic, $"Type '{TypeName}' should be public"); - var unhandledExceptionMethod = foundType.Methods - .FirstOrDefault (m => string.Equals (m.Name, expectedMethodName, StringComparison.Ordinal)); + // Additionally verify that the UnhandledException method is also public + const string MethodName = "UnhandledException"; + MethodDefinition? method = null; + foreach (MethodDefinition m in type.Methods) { + if (m.Name.Equals (MethodName, StringComparison.Ordinal)) { + method = m; + break; + } + } - Assert.IsNotNull (unhandledExceptionMethod, $"Method '{expectedMethodName}' should exist in type '{expectedTypeName}'"); - Assert.IsTrue (unhandledExceptionMethod.IsPublic, $"Method '{expectedMethodName}' should be public"); + Assert.NotNull (method, $"Failed to find the '{MethodName}' method in type '{TypeName}'"); + Assert.IsTrue (method.IsPublic, $"Method '{MethodName}' should be public"); } } } From e9a4e22c269e6b5b117fb7abd1020d37f6b8d30d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 22:36:07 +0000 Subject: [PATCH 5/5] Remove DebuggerBrowsable attributes - only valid on properties/fields Co-authored-by: Marek Habersack Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> --- .../Android.Runtime/AndroidEnvironmentInternal.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs index 723dbdfe6d9..682959e90e5 100644 --- a/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs +++ b/src/Mono.Android.Runtime/Android.Runtime/AndroidEnvironmentInternal.cs @@ -1,16 +1,13 @@ using System; using System.ComponentModel; -using System.Diagnostics; namespace Android.Runtime { - [DebuggerBrowsable (DebuggerBrowsableState.Never)] [EditorBrowsable (EditorBrowsableState.Never)] public static class AndroidEnvironmentInternal { internal static Action? UnhandledExceptionHandler; - [DebuggerBrowsable (DebuggerBrowsableState.Never)] [EditorBrowsable (EditorBrowsableState.Never)] public static void UnhandledException (Exception e) {