diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index ee5d087206a..86ff851d804 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -356,6 +356,13 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy) return; } + // Abstract types are never directly instantiated from Java — the ACW + // constructor's getClass() guard prevents activation. Skip generating + // UCO constructor wrappers for them. + if (peer.IsAbstract) { + return; + } + foreach (var ctor in peer.JavaConstructors) { if (ctor.SuperArgumentsString != null && !ctor.HasMatchingManagedCtor) { throw new InvalidOperationException ( diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs index 48cb40876d6..93195ece435 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/TrimmableTypeMapBuildTests.cs @@ -394,5 +394,36 @@ class ExportShapes : Java.Lang.Object { "assembly or the user's [Export] source. Offending warning lines:\n " + string.Join ("\n ", offending)); } + + [Test] + public void Build_WithTrimmableTypeMap_AbstractTypeWithProtectedCtor_Succeeds () + { + if (IgnoreUnsupportedConfiguration (AndroidRuntime.NativeAOT, release: true)) { + return; + } + + var proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.SetRuntime (AndroidRuntime.NativeAOT); + proj.SetProperty ("_AndroidTypeMapImplementation", "trimmable"); + proj.Sources.Add (new BuildItem.Source ("AbstractProvider.cs") { + TextContent = () => @" +namespace UnnamedProject { + public abstract class AbstractProvider : Java.Lang.Object { + protected AbstractProvider (Android.Content.Context context) { } + public abstract string GetData (); + } + + public class ConcreteProvider : AbstractProvider { + public ConcreteProvider (Android.Content.Context context) : base (context) { } + public override string GetData () => ""hello""; + } +}" + }); + + using var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "Build should have succeeded — abstract types with protected ctors should not cause XAGTT7009."); + } } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs index 06498daa478..5eb8a16f30c 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs @@ -1369,6 +1369,26 @@ public void Build_ExportConstructorWithoutMatchingManagedCtor_Throws () Assert.Contains ("no matching user-visible managed constructor", ex.Message); Assert.Contains ("MyApp.MissingCtor", ex.Message); } + + [Fact] + public void Build_AbstractTypeWithProtectedCtor_NoUcoConstructors () + { + var peer = MakeAcwPeer ("my/app/AbstractAdapter", "MyApp.AbstractAdapter", "App") with { + IsAbstract = true, + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = "(Landroid/content/Context;)V", + HasMatchingManagedCtor = false, + SuperArgumentsString = "p0", + }, + }, + }; + var model = BuildModel (new [] { peer }); + var proxy = model.ProxyTypes.FirstOrDefault (p => p.TypeName.Contains ("AbstractAdapter")); + Assert.NotNull (proxy); + Assert.Empty (proxy.UcoConstructors); + } } public class NativeRegistrations