From 87ff900ff0b21c0732bd9b6225bdfb651a06112b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:43:31 +0000 Subject: [PATCH 1/3] Initial plan From 2e52c08d4de2588e8cf7dde9c144a368b4225ec8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:21:28 +0000 Subject: [PATCH 2/3] Add feature switch to disable XA_HTTP_CLIENT_HANDLER_TYPE environment variable by default - Add XaHttpClientHandlerType feature switch to RuntimeFeature.cs (disabled by default) - Update GetHttpMessageHandler() to return AndroidMessageHandler directly when feature disabled - Add ILLink substitution for the new feature switch - Add RuntimeHostConfigurationOption for the feature switch - Remove default value for AndroidHttpClientHandlerType MSBuild property - Add XA1042 error for NativeAOT when AndroidHttpClientHandlerType is set - Add XA1043 deprecation warning for MonoVM/CoreCLR when AndroidHttpClientHandlerType is set - Add XA1044 warning when XA_HTTP_CLIENT_HANDLER_TYPE is found in environment files - Update tests to reflect new behavior - Add documentation for new error/warning codes Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- Documentation/docs-mobile/messages/xa1042.md | 31 ++++++++++ Documentation/docs-mobile/messages/xa1043.md | 29 ++++++++++ Documentation/docs-mobile/messages/xa1044.md | 31 ++++++++++ .../Android.Runtime/AndroidEnvironment.cs | 11 ++++ .../ILLink/ILLink.Substitutions.xml | 2 + .../RuntimeFeature.cs | 6 ++ ...soft.Android.Sdk.DefaultProperties.targets | 4 +- ...icrosoft.Android.Sdk.RuntimeConfig.targets | 4 ++ .../Properties/Resources.resx | 14 +++++ .../EnvironmentContentTests.cs | 53 ++++++++++++------ .../Tasks/CheckClientHandlerTypeTests.cs | 56 ++++++++++--------- .../Utilities/EnvironmentFilesParser.cs | 4 +- .../Xamarin.Android.Common.targets | 21 ++++++- .../Android.Runtime/AndroidEnvironmentTest.cs | 38 +++---------- 14 files changed, 227 insertions(+), 77 deletions(-) create mode 100644 Documentation/docs-mobile/messages/xa1042.md create mode 100644 Documentation/docs-mobile/messages/xa1043.md create mode 100644 Documentation/docs-mobile/messages/xa1044.md diff --git a/Documentation/docs-mobile/messages/xa1042.md b/Documentation/docs-mobile/messages/xa1042.md new file mode 100644 index 00000000000..289393f4b6a --- /dev/null +++ b/Documentation/docs-mobile/messages/xa1042.md @@ -0,0 +1,31 @@ +--- +title: .NET for Android error XA1042 +description: XA1042 error code +ms.date: 01/29/2026 +--- +# .NET for Android error XA1042 + +## Example messages + +``` +The 'AndroidHttpClientHandlerType' property is deprecated and not supported with NativeAOT. The property value 'System.Net.Http.SocketsHttpHandler, System.Net.Http' will be ignored, and the default handler 'Xamarin.Android.Net.AndroidMessageHandler' will be used. Remove the 'AndroidHttpClientHandlerType' property from your project file. To use a custom HTTP handler, pass it to the HttpClient constructor. +``` + +## Issue + +The `AndroidHttpClientHandlerType` MSBuild property is not compatible with NativeAOT because type name resolution at runtime requires reflection, which cannot be safely used with ahead-of-time compilation. + +When targeting NativeAOT, the default `Xamarin.Android.Net.AndroidMessageHandler` handler will always be used regardless of the `AndroidHttpClientHandlerType` property value. + +## Solution + +Remove the `AndroidHttpClientHandlerType` property from your project file. + +If you need to use a custom HTTP message handler, pass it directly to the `HttpClient` constructor: + +```csharp +var handler = new MyCustomHttpHandler(); +var client = new HttpClient(handler); +``` + +This approach is compatible with all .NET for Android runtimes including NativeAOT, CoreCLR, and MonoVM. diff --git a/Documentation/docs-mobile/messages/xa1043.md b/Documentation/docs-mobile/messages/xa1043.md new file mode 100644 index 00000000000..3436c9cf3b9 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa1043.md @@ -0,0 +1,29 @@ +--- +title: .NET for Android warning XA1043 +description: XA1043 warning code +ms.date: 01/29/2026 +--- +# .NET for Android warning XA1043 + +## Example messages + +``` +The 'AndroidHttpClientHandlerType' property is deprecated and will be removed in a future version of .NET for Android. The property value 'System.Net.Http.SocketsHttpHandler, System.Net.Http' will be used, but the recommended approach is to remove the property and pass a custom HTTP handler to the HttpClient constructor if needed. +``` + +## Issue + +The `AndroidHttpClientHandlerType` MSBuild property is deprecated because it relies on runtime type name resolution which is not compatible with trimming and ahead-of-time compilation. While the property still works for MonoVM and CoreCLR runtimes, it will be removed in a future version of .NET for Android. + +## Solution + +Remove the `AndroidHttpClientHandlerType` property from your project file. + +If you need to use a custom HTTP message handler, pass it directly to the `HttpClient` constructor: + +```csharp +var handler = new MyCustomHttpHandler(); +var client = new HttpClient(handler); +``` + +This approach is compatible with all .NET for Android runtimes including NativeAOT, CoreCLR, and MonoVM, and works correctly with trimming and ahead-of-time compilation. diff --git a/Documentation/docs-mobile/messages/xa1044.md b/Documentation/docs-mobile/messages/xa1044.md new file mode 100644 index 00000000000..a3737633ee7 --- /dev/null +++ b/Documentation/docs-mobile/messages/xa1044.md @@ -0,0 +1,31 @@ +--- +title: .NET for Android warning XA1044 +description: XA1044 warning code +ms.date: 01/29/2026 +--- +# .NET for Android warning XA1044 + +## Example messages + +``` +The 'XA_HTTP_CLIENT_HANDLER_TYPE' environment variable is deprecated and will be removed in a future version of .NET for Android. Remove the environment variable from your @(AndroidEnvironment) files. To use a custom HTTP handler, pass it to the HttpClient constructor. +``` + +## Issue + +The `XA_HTTP_CLIENT_HANDLER_TYPE` environment variable is deprecated because it relies on runtime type name resolution which is not compatible with trimming and ahead-of-time compilation. + +This warning is raised when the build system detects that an `@(AndroidEnvironment)` file contains the `XA_HTTP_CLIENT_HANDLER_TYPE` environment variable. + +## Solution + +Remove the `XA_HTTP_CLIENT_HANDLER_TYPE` environment variable from your `@(AndroidEnvironment)` files. + +If you need to use a custom HTTP message handler, pass it directly to the `HttpClient` constructor: + +```csharp +var handler = new MyCustomHttpHandler(); +var client = new HttpClient(handler); +``` + +This approach is compatible with all .NET for Android runtimes including NativeAOT, CoreCLR, and MonoVM, and works correctly with trimming and ahead-of-time compilation. diff --git a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs index 761a9cbde86..16cc1f2933f 100644 --- a/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs +++ b/src/Mono.Android/Android.Runtime/AndroidEnvironment.cs @@ -15,6 +15,7 @@ using Java.Net; using Java.Security; using Javax.Net.Ssl; +using Microsoft.Android.Runtime; namespace Android.Runtime { @@ -245,6 +246,16 @@ static void DetectCPUAndArchitecture (out ushort builtForCPU, out ushort running // DO NOT REMOVE [DynamicDependency (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof (Xamarin.Android.Net.AndroidMessageHandler))] static HttpMessageHandler GetHttpMessageHandler () + { + if (!RuntimeFeature.XaHttpClientHandlerType) { + return new Xamarin.Android.Net.AndroidMessageHandler (); + } + + return GetHttpMessageHandlerFromEnvironment (); + } + + [RequiresUnreferencedCode ("The handler type specified in XA_HTTP_CLIENT_HANDLER_TYPE might be removed by the trimmer.")] + static HttpMessageHandler GetHttpMessageHandlerFromEnvironment () { [UnconditionalSuppressMessage ("Trimming", "IL2057", Justification = "Preserved by the MarkJavaObjects trimmer step.")] [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] diff --git a/src/Mono.Android/ILLink/ILLink.Substitutions.xml b/src/Mono.Android/ILLink/ILLink.Substitutions.xml index 239252fe937..1e6da54c50d 100644 --- a/src/Mono.Android/ILLink/ILLink.Substitutions.xml +++ b/src/Mono.Android/ILLink/ILLink.Substitutions.xml @@ -7,6 +7,8 @@ + + diff --git a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs index 24bedf65bed..6e310b782ac 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/RuntimeFeature.cs @@ -10,6 +10,7 @@ static class RuntimeFeature const bool IsCoreClrRuntimeEnabledByDefault = false; const bool IsAssignableFromCheckEnabledByDefault = true; const bool StartupHookSupportEnabledByDefault = true; + const bool XaHttpClientHandlerTypeEnabledByDefault = false; const string FeatureSwitchPrefix = "Microsoft.Android.Runtime.RuntimeFeature."; const string StartupHookProviderSwitch = "System.StartupHookProvider.IsSupported"; @@ -34,4 +35,9 @@ static class RuntimeFeature [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] internal static bool StartupHookSupport { get; } = AppContext.TryGetSwitch (StartupHookProviderSwitch, out bool isEnabled) ? isEnabled : StartupHookSupportEnabledByDefault; + + [FeatureSwitchDefinition ($"{FeatureSwitchPrefix}{nameof (XaHttpClientHandlerType)}")] + [FeatureGuard (typeof (RequiresUnreferencedCodeAttribute))] + internal static bool XaHttpClientHandlerType { get; } = + AppContext.TryGetSwitch ($"{FeatureSwitchPrefix}{nameof (XaHttpClientHandlerType)}", out bool isEnabled) ? isEnabled : XaHttpClientHandlerTypeEnabledByDefault; } diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets index 54b5da57f95..3ea4e80c24e 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets @@ -7,7 +7,7 @@ Assets Resource true - Xamarin.Android.Net.AndroidMessageHandler + true true false @@ -133,7 +133,7 @@ false false true - false + false true false true diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets index 0dddb3a9144..59827b6fafa 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.RuntimeConfig.targets @@ -51,6 +51,10 @@ See: https://github.com/dotnet/runtime/blob/b13715b6984889a709ba29ea8a1961db469f Value="$([MSBuild]::ValueOrDefault('$(_AndroidUseManagedTypeMap)', 'false'))" Trim="true" /> + + + The 'AndroidHttpClientHandlerType' property is deprecated and not supported with NativeAOT. The property value '{0}' will be ignored, and the default handler 'Xamarin.Android.Net.AndroidMessageHandler' will be used. Remove the 'AndroidHttpClientHandlerType' property from your project file. To use a custom HTTP handler, pass it to the HttpClient constructor. + The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'NativeAOT', 'Xamarin.Android.Net.AndroidMessageHandler', 'HttpClient'. +{0} - The value of the AndroidHttpClientHandlerType property. + + + The 'AndroidHttpClientHandlerType' property is deprecated and will be removed in a future version of .NET for Android. The property value '{0}' will be used, but the recommended approach is to remove the property and pass a custom HTTP handler to the HttpClient constructor if needed. + The following are literal names and should not be translated: 'AndroidHttpClientHandlerType', 'HttpClient'. +{0} - The value of the AndroidHttpClientHandlerType property. + + + The 'XA_HTTP_CLIENT_HANDLER_TYPE' environment variable is deprecated and will be removed in a future version of .NET for Android. Remove the environment variable from your @(AndroidEnvironment) files. To use a custom HTTP handler, pass it to the HttpClient constructor. + The following are literal names and should not be translated: 'XA_HTTP_CLIENT_HANDLER_TYPE', '@(AndroidEnvironment)', 'HttpClient'. + Java dependency '{0}' is not satisfied. The following are literal names and should not be translated: Java. diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs index 1f5d51ef909..32a5df11226 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/EnvironmentContentTests.cs @@ -162,9 +162,11 @@ public void CheckConcurrentGC () } [Test] - public void CheckForInvalidHttpClientHandlerType ([Values] AndroidRuntime runtime) + public void CheckForInvalidHttpClientHandlerType () { + // Test with MonoVM only since NativeAOT will fail with XA1042 before reaching XA1031 const bool isRelease = true; + var runtime = AndroidRuntime.MonoVM; if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } @@ -177,13 +179,38 @@ public void CheckForInvalidHttpClientHandlerType ([Values] AndroidRuntime runtim proj.SetProperty ("AndroidHttpClientHandlerType", "Android.App.Application"); Assert.IsFalse (b.Build (proj), "Build should not have succeeded."); Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "XA1031"), "Output should contain XA1031"); + // Also expect deprecation warning + Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "XA1043"), "Output should contain XA1043 deprecation warning"); } } [Test] - public void CheckHttpClientHandlerType ([Values] AndroidRuntime runtime) + public void CheckHttpClientHandlerType_NativeAOT_Error () { - bool isRelease = runtime == AndroidRuntime.NativeAOT; + // NativeAOT should fail with an error when AndroidHttpClientHandlerType is set + const bool isRelease = true; + var runtime = AndroidRuntime.NativeAOT; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } + var proj = new XamarinAndroidApplicationProject () { + IsRelease = isRelease, + }; + proj.SetRuntime (runtime); + using (var b = CreateApkBuilder ()) { + b.ThrowOnBuildFailure = false; + proj.SetProperty ("AndroidHttpClientHandlerType", "Xamarin.Android.Net.AndroidMessageHandler"); + Assert.IsFalse (b.Build (proj), "Build should not have succeeded for NativeAOT with AndroidHttpClientHandlerType set."); + Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "XA1042"), "Output should contain XA1042"); + } + } + + [Test] + [TestCase (AndroidRuntime.MonoVM)] + [TestCase (AndroidRuntime.CoreCLR)] + public void CheckHttpClientHandlerType_DeprecationWarning (AndroidRuntime runtime) + { + bool isRelease = false; if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { return; } @@ -197,7 +224,6 @@ public void CheckHttpClientHandlerType ([Values] AndroidRuntime runtime) string supportedAbis = runtime switch { AndroidRuntime.MonoVM => "armeabi-v7a;arm64-v8a", AndroidRuntime.CoreCLR => "arm64-v8a;x86_64", - AndroidRuntime.NativeAOT => "arm64-v8a;x86_64", _ => throw new NotSupportedException ($"Unsupported runtime '{runtime}'") }; proj.SetRuntime (runtime); @@ -208,17 +234,16 @@ public void CheckHttpClientHandlerType ([Values] AndroidRuntime runtime) using (var b = CreateApkBuilder ()) { proj.SetProperty ("AndroidHttpClientHandlerType", expectedDefaultValue); Assert.IsTrue (b.Build (proj), "Build should have succeeded."); + // Expect deprecation warning XA1043 + Assert.IsTrue (StringAssertEx.ContainsText (b.LastBuildOutput, "XA1043"), "Output should contain XA1043 deprecation warning"); + var intermediateOutputDir = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath); List? envFiles = null; Dictionary envvars; - if (runtime == AndroidRuntime.NativeAOT) { - envvars = EnvironmentHelper.ReadNativeAotEnvironmentVariables (intermediateOutputDir); - } else { - envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true, runtime); - envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles, runtime); - } + envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true, runtime); + envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles, runtime); Assert.IsTrue (envvars.ContainsKey (httpClientHandlerVarName), $"Environment should contain '{httpClientHandlerVarName}'."); Assert.AreEqual (expectedDefaultValue, envvars[httpClientHandlerVarName]); @@ -226,12 +251,8 @@ public void CheckHttpClientHandlerType ([Values] AndroidRuntime runtime) proj.SetProperty ("AndroidHttpClientHandlerType", expectedUpdatedValue); Assert.IsTrue (b.Build (proj), "Second build should have succeeded."); - if (runtime == AndroidRuntime.NativeAOT) { - envvars = EnvironmentHelper.ReadNativeAotEnvironmentVariables (intermediateOutputDir); - } else { - envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true, runtime); - envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles, runtime); - } + envFiles = EnvironmentHelper.GatherEnvironmentFiles (intermediateOutputDir, supportedAbis, true, runtime); + envvars = EnvironmentHelper.ReadEnvironmentVariables (envFiles, runtime); Assert.IsTrue (envvars.ContainsKey (httpClientHandlerVarName), $"Environment should contain '{httpClientHandlerVarName}'."); Assert.AreEqual (expectedUpdatedValue, envvars[httpClientHandlerVarName]); diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs index 87616d10078..fe6e956845a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckClientHandlerTypeTests.cs @@ -14,11 +14,11 @@ namespace Xamarin.Android.Build.Tests { public class CheckClientHandlerTypeTests : BaseTest { - static IEnumerable Get_ErrorIsNotRaised_Data () + static IEnumerable Get_DeprecationWarningIsRaised_Data () { var ret = new List (); - foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { + foreach (AndroidRuntime runtime in new[] { AndroidRuntime.MonoVM, AndroidRuntime.CoreCLR }) { AddTestData ("Xamarin.Android.Net.AndroidMessageHandler", runtime); AddTestData ("System.Net.Http.SocketsHttpHandler, System.Net.Http", runtime); } @@ -35,8 +35,8 @@ void AddTestData (string handler, AndroidRuntime runtime) } [Test] - [TestCaseSource (nameof (Get_ErrorIsNotRaised_Data))] - public void ErrorIsNotRaised (string handler, AndroidRuntime runtime) + [TestCaseSource (nameof (Get_DeprecationWarningIsRaised_Data))] + public void DeprecationWarningIsRaised (string handler, AndroidRuntime runtime) { const bool isRelease = false; if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { @@ -45,9 +45,6 @@ public void ErrorIsNotRaised (string handler, AndroidRuntime runtime) string path = Path.Combine (Root, "temp", TestName); Directory.CreateDirectory (path); - string intermediatePath; - bool shouldSkip = handler.Contains ("Xamarin.Android.Net.AndroidMessageHandler"); - bool targetSkipped; var proj = new XamarinAndroidApplicationProject () { IsRelease = isRelease, }; @@ -56,36 +53,41 @@ public void ErrorIsNotRaised (string handler, AndroidRuntime runtime) using (var b = CreateApkBuilder (path)) { b.Verbosity = LoggerVerbosity.Detailed; b.Build (proj); - intermediatePath = Path.Combine (path,proj.IntermediateOutputPath); - targetSkipped = b.Output.IsTargetSkipped ("_CheckAndroidHttpClientHandlerType", defaultIfNotUsed: shouldSkip); + // Should emit deprecation warning XA1043 for MonoVM/CoreCLR + Assert.IsTrue (b.LastBuildOutput.ContainsText ("XA1043"), "Expected deprecation warning XA1043"); } + } - if (shouldSkip) - Assert.IsTrue (targetSkipped, "_CheckAndroidHttpClientHandlerType should not have run."); - else - Assert.IsFalse (targetSkipped, "_CheckAndroidHttpClientHandlerType should have run."); + [Test] + public void NativeAOT_ErrorIsRaised_WhenHttpClientHandlerTypeSet () + { + const bool isRelease = true; + var runtime = AndroidRuntime.NativeAOT; + if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) { + return; + } - string asmPath = Path.GetFullPath (Path.Combine (intermediatePath, "android", "assets")); - var errors = new List (); - var warnings = new List (); - List assemblies = new List (); - string[] files = Directory.GetFiles (asmPath, "*.dll", SearchOption.AllDirectories); - foreach (var file in files) - assemblies.Add (new TaskItem (file)); - IBuildEngine4 engine = new MockBuildEngine (System.Console.Out, errors, warnings); - var task = new CheckClientHandlerType () { - BuildEngine = engine, - ClientHandlerType = handler, - ResolvedAssemblies = assemblies.ToArray (), + string path = Path.Combine (Root, "temp", TestName); + Directory.CreateDirectory (path); + var proj = new XamarinAndroidApplicationProject () { + IsRelease = isRelease, }; - Assert.True (task.Execute (), $"task should have succeeded. {string.Join (";", errors.Select (x => x.Message))}"); + proj.SetRuntime (runtime); + proj.SetProperty ("AndroidHttpClientHandlerType", "System.Net.Http.SocketsHttpHandler, System.Net.Http"); + using (var b = CreateApkBuilder (path)) { + b.Verbosity = LoggerVerbosity.Detailed; + b.ThrowOnBuildFailure = false; + b.Build (proj); + // Should emit error XA1042 for NativeAOT + Assert.IsTrue (b.LastBuildOutput.ContainsText ("XA1042"), "Expected error XA1042 for NativeAOT"); + } } static IEnumerable Get_ErrorIsRaised_Data () { var ret = new List (); - foreach (AndroidRuntime runtime in Enum.GetValues (typeof (AndroidRuntime))) { + foreach (AndroidRuntime runtime in new[] { AndroidRuntime.MonoVM, AndroidRuntime.CoreCLR }) { AddTestData ("Xamarin.Android.Net.AndroidClientHandler", runtime); } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs index 0aecd7b1434..420ba06464a 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/EnvironmentFilesParser.cs @@ -51,8 +51,10 @@ public void Parse (ITaskItem[]? environments, SequencePointsMode sequencePointsM if (sequencePointsMode != SequencePointsMode.None && !lineToWrite.Contains ("gen-compact-seq-points")) lineToWrite = line + ",gen-compact-seq-points"; } - if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) + if (lineToWrite.StartsWith ("XA_HTTP_CLIENT_HANDLER_TYPE=", StringComparison.Ordinal)) { HaveHttpMessageHandler = true; + log.LogCodedWarning ("XA1044", Properties.Resources.XA1044); + } if (lineToWrite.StartsWith ("mono.enable_assembly_preload=", StringComparison.Ordinal)) { int idx = lineToWrite.IndexOf ('='); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 45a98f688d6..67fe65fcbd4 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -477,11 +477,30 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. + + Condition=" '$(AndroidHttpClientHandlerType)' != '' And '$(AndroidApplication)' == 'True' "> + + + + + diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs index 76ff2e50b69..0d7540c9466 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Android.Runtime/AndroidEnvironmentTest.cs @@ -25,45 +25,23 @@ public void TearDown () ClearHttpMessageHandlerTypeCache (); } + // When the XaHttpClientHandlerType feature switch is disabled (the default), + // GetHttpMessageHandler always returns AndroidMessageHandler regardless of the + // XA_HTTP_CLIENT_HANDLER_TYPE environment variable value. [Test] [TestCase (null)] - [TestCase ("Xamarin.Android.Net.AndroidHttpResponseMessage")] // does not extend HttpMessageHandler - // instantiating AndroidClientHandler or HttpClientHandler (or any other type extending HttpClientHandler) - // would cause infinite recursion in the .NET build and so it is replaced with AndroidMessageHandler - [TestCase ("System.Net.Http.HttpClientHandler, System.Net.Http")] + [TestCase ("Xamarin.Android.Net.AndroidHttpResponseMessage")] [TestCase ("Xamarin.Android.Net.AndroidClientHandler")] - public void GetHttpMessageHandler_FallbackToAndroidMessageHandler (string? typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - Assert.IsNotNull ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); - } - - [Test] - [TestCase ("System.Net.Http.HttpClientHandler")] // the type name doesn't contain the name of the assembly so the type won't be found + [TestCase ("System.Net.Http.HttpClientHandler, System.Net.Http")] + [TestCase ("System.Net.Http.HttpClientHandler")] [TestCase ("Some.Nonexistent.Type")] - public void GetHttpMessageHandler_FallbackForInaccessibleTypes (string typeName) - { - var handler = GetHttpMessageHandler (typeName); - - Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - Assert.IsNotNull ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); - } - - [Test] - [TestCase ("Xamarin.Android.Net.AndroidMessageHandler")] [TestCase ("System.Net.Http.SocketsHttpHandler, System.Net.Http")] - public void GetHttpMessageHandler_OverridesDefaultValue (string typeName) + public void GetHttpMessageHandler_IgnoresEnvironmentVariable_WhenFeatureDisabled (string? typeName) { var handler = GetHttpMessageHandler (typeName); Assert.IsNotNull (handler, "GetHttpMessageHandler returned null"); - - // type's FullName doesn't contain the assembly name - var indexOfComma = typeName.IndexOf(','); - var expectedTypeName = indexOfComma > 0 ? typeName.Substring(0, indexOfComma) : typeName; - Assert.AreEqual (expectedTypeName, handler.GetType ().FullName); + Assert.AreEqual ("Xamarin.Android.Net.AndroidMessageHandler", handler.GetType ().FullName); } private static object? GetHttpMessageHandler (string? typeName) From 4d5f2f84b0ab1c1d11b758e5100c5249c6caad41 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:26:30 +0000 Subject: [PATCH 3/3] Update PreserveCustomHttpClientHandlers test to exclude NativeAOT Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com> --- .../Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs index 1c995304248..b6587d5e5a6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/LinkerTests.cs @@ -225,7 +225,11 @@ class MyCustomHandler : System.Net.Http.HttpMessageHandler } [Test] - public void PreserveCustomHttpClientHandlers ([Values (TrimMode.Partial, TrimMode.Full)] TrimMode trimMode, [Values] AndroidRuntime runtime) + [TestCase (TrimMode.Partial, AndroidRuntime.MonoVM)] + [TestCase (TrimMode.Full, AndroidRuntime.MonoVM)] + [TestCase (TrimMode.Partial, AndroidRuntime.CoreCLR)] + [TestCase (TrimMode.Full, AndroidRuntime.CoreCLR)] + public void PreserveCustomHttpClientHandlers (TrimMode trimMode, AndroidRuntime runtime) { PreserveCustomHttpClientHandler ("Xamarin.Android.Net.AndroidMessageHandler", "", $"temp/PreserveAndroidMessageHandler{trimMode}{runtime}", "android-arm64/linked/Mono.Android.dll", trimMode, runtime);