From ad5ad0f314532e1032ddbd7895893329e1dc58fb Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 13 Oct 2025 16:55:04 -0500 Subject: [PATCH 01/17] [xabt] additional fixes for non-integer API levels We found the following errors with many `javac` errors: class MyTextObjectFont : Android.Graphics.Pdf.Component.PdfPageTextObjectFont { public MyTextObjectFont(Android.Graphics.Pdf.Component.PdfPageTextObjectFont font) : base(font) { } } Such as: obj\Debug\net10.0-android36.1\android\src\crc643b87557a63e7c027\MainActivity_MyTextObjectFont.java(5,40): error JAVAC0000: error: package android.graphics.pdf.component does not exist extends android.graphics.pdf.component.PdfPageTextObjectFont This appears to be caused by the use of: "C:\Program Files (x86)\Android\android-sdk\platforms\android-36\android.jar" When the build *should* be using: "C:\Program Files (x86)\Android\android-sdk\platforms\android-36.1\android.jar" Update various MSBuild logic to handle non-integer API levels. Co-authored-by: Jonathan Pryor --- .../Tasks/GetAndroidDefineConstants.cs | 15 +++++++++-- .../Tasks/GetAotArguments.cs | 4 +-- .../Tasks/ResolveAndroidTooling.cs | 2 +- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 27 +++++++++++++++++++ .../Utilities/MonoAndroidHelper.cs | 14 ++++++++++ .../Tests/InstallAndRunTests.cs | 3 +-- 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAndroidDefineConstants.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAndroidDefineConstants.cs index 8699d0af0d0..76d74ce66cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAndroidDefineConstants.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAndroidDefineConstants.cs @@ -15,7 +15,7 @@ public class GetAndroidDefineConstants : AndroidTask public override string TaskPrefix => "GAD"; [Required] - public int AndroidApiLevel { get; set; } + public string AndroidApiLevel { get; set; } = ""; public string? ProductVersion { get; set; } @@ -34,9 +34,20 @@ public override bool RunTask () constants.Add (new TaskItem ("__MOBILE__")); constants.Add (new TaskItem ("__ANDROID__")); - for (int i = 1; i <= AndroidApiLevel; ++i) { + if (!MonoAndroidHelper.TryParseApiLevel (AndroidApiLevel, out var apiLevel)) { + return false; + } + + for (int i = 1; i <= apiLevel.Major; ++i) { constants.Add (new TaskItem ($"__ANDROID_{i}__")); } + // TODO: We're just going to assume that there is a minor release for every major release from API-36.1 onward… + for (int i = 36; i < apiLevel.Major; ++i) { + constants.Add (new TaskItem ($"__ANDROID_{i}_1__")); + } + if (apiLevel.Minor != 0) { + constants.Add (new TaskItem ($"__ANDROID_{apiLevel.Major}_{apiLevel.Minor}__")); + } AndroidDefineConstants = constants.ToArray (); diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs index ea02a64f93e..61d407cb9b5 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetAotArguments.cs @@ -148,8 +148,8 @@ int GetNdkApiLevel (NdkTools ndk, AndroidTargetArch arch) level = manifest.MinSdkVersion.Value; } else if (int.TryParse (MinimumSupportedApiLevel, out level)) { // level already set - } else if (int.TryParse (AndroidApiLevel, out level)) { - // level already set + } else if (MonoAndroidHelper.TryParseApiLevel (AndroidApiLevel, out Version version)) { + level = version.Major; } else { // Probably not ideal! level = MonoAndroidHelper.SupportedVersions.MaxStableVersion?.ApiLevel ?? 21; diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs index 5ea189b934c..a805675e21d 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs @@ -81,7 +81,7 @@ public override bool RunTask () { // This should be 31.0, 32.0, etc. if (Version.TryParse (TargetPlatformVersion, out Version v)) { - AndroidApiLevel = v.Major.ToString (); + AndroidApiLevel = v.ToString (); } else { AndroidApiLevel = GetMaxStableApiLevel ().ToString (); } 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 6e8c70e7d1d..8b40cece5bd 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 @@ -1744,5 +1744,32 @@ public void Plugin_Maui_Audio () const string className = "Lcrc64467b05f37239e7a6/StreamMediaDataSource;"; Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, AndroidSdkPath), $"`{dexFile}` should include `{className}`!"); } + + [Test] + public void SubclassMinorApiLevels ([Values ("net10.0-android36.1")] string targetFramework) + { + var proj = new XamarinAndroidApplicationProject () { + TargetFramework = targetFramework, + ExtraNuGetConfigSources = { + Path.Combine (XABuildPaths.BuildOutputDirectory, "nuget-unsigned"), + } + }; + + // TODO: update on new minor API levels to use an introduced minor API + proj.MainActivity = proj.DefaultMainActivity + .Replace ("//${USINGS}", "using Android.Graphics.Pdf.Component;") + .Replace ("//${AFTER_MAINACTIVITY}", """ + class MyTextObjectFont : PdfPageTextObjectFont + { + public MyTextObjectFont (PdfPageTextObjectFont font) : base (font) + { + } + } + """); + + + var builder = CreateApkBuilder (); + Assert.IsTrue (builder.Build (proj), "`dotnet build` should succeed"); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs index 6634fa4acee..13102b06955 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs @@ -595,6 +595,20 @@ public static int ConvertSupportedOSPlatformVersionToApiLevel (string version) return apiLevel; } + public static bool TryParseApiLevel (string apiLevel, out Version version) + { + if (Version.TryParse (apiLevel, out var v)) { + version = v; + return true; + } + if (int.TryParse (apiLevel, out var major)) { + version = new Version (major, 0); + return true; + } + version = null; + return false; + } + #if MSBUILD public static string GetAssemblyAbi (ITaskItem asmItem) { diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 80c6c5dbe8f..75518adace2 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -949,7 +949,7 @@ public void DotNetInstallAndRunPreviousSdk ( } [Test] - public void DotNetInstallAndRunPreviewAPILevels ( + public void DotNetInstallAndRunMinorAPILevels ( [Values (false, true)] bool isRelease, [Values ("net10.0-android36.1")] string targetFramework) { @@ -960,7 +960,6 @@ public void DotNetInstallAndRunPreviewAPILevels ( Path.Combine (XABuildPaths.BuildOutputDirectory, "nuget-unsigned"), } }; - proj.SetProperty ("EnablePreviewFeatures", "true"); // TODO: update on new minor API levels to use an introduced minor API proj.MainActivity = proj.DefaultMainActivity From e2cf47a58cef73eced60a940eea2336d3b2d5c6b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Oct 2025 09:48:51 -0500 Subject: [PATCH 02/17] Fix `` --- .../Microsoft.Android.Sdk.Tooling.targets | 2 +- .../Tasks/CheckGoogleSdkRequirements.cs | 31 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Tooling.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Tooling.targets index e33e9215ff6..e554aec324c 100644 --- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Tooling.targets +++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.Tooling.targets @@ -70,7 +70,7 @@ This file contains .NET 6+ calls to the and diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CheckGoogleSdkRequirements.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CheckGoogleSdkRequirements.cs index bdea41871d4..88694667e86 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/CheckGoogleSdkRequirements.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/CheckGoogleSdkRequirements.cs @@ -1,15 +1,8 @@ #nullable enable using System; -using System.Collections.Generic; -using Microsoft.Build.Utilities; -using Microsoft.Build.Framework; -using System.IO; -using System.Linq; - -using Java.Interop.Tools.Cecil; -using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; namespace Xamarin.Android.Tasks { @@ -17,15 +10,8 @@ public class CheckGoogleSdkRequirements : AndroidTask { public override string TaskPrefix => "CGS"; - /// - /// This will be blank for .NET 5 builds - /// - public string? TargetFrameworkVersion { get; set; } - - /// - /// This is used instead of TargetFrameworkVersion for .NET 5 builds - /// - public int ApiLevel { get; set; } + [Required] + public string AndroidApiLevel { get; set; } = ""; [Required] public string ManifestFile { get; set; } = ""; @@ -34,15 +20,18 @@ public override bool RunTask () { ManifestDocument manifest = new ManifestDocument (ManifestFile); - var compileSdk = TargetFrameworkVersion.IsNullOrEmpty () ? - ApiLevel : - MonoAndroidHelper.SupportedVersions.GetApiLevelFromFrameworkVersion (TargetFrameworkVersion); + int? compileSdk = null; + + if (MonoAndroidHelper.TryParseApiLevel (AndroidApiLevel, out Version version)) { + compileSdk = version.Major; + } if (!int.TryParse (manifest.GetMinimumSdk (), out int minSdk)) { minSdk = 1; } if (!int.TryParse (manifest.GetTargetSdk (), out int targetSdk)) { - targetSdk = compileSdk ?? ApiLevel; + // 21 is minimum supported for .NET 6+, but should be better than putting 1 here. + targetSdk = compileSdk ?? 21; } //We should throw a warning if the targetSdkVersion is lower than compileSdkVersion(TargetFrameworkVersion). From be3c4dcf4bbd3164c63ede9b74cdef4e2d8dad5b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Oct 2025 11:05:35 -0500 Subject: [PATCH 03/17] Update CheckGoogleSdkRequirementsTests.cs --- .../Tasks/CheckGoogleSdkRequirementsTests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs index 5d5e9a6d8c4..e85bb3e070b 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs @@ -49,7 +49,6 @@ public void CheckManifestIsOK () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, - TargetFrameworkVersion = "v9.0", ManifestFile = CreateManiestFile (10, 28), }; Assert.True (task.Execute (), "Task should have succeeded."); @@ -62,7 +61,6 @@ public void CheckManifestTargetSdkLowerThanCompileSdk () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, - TargetFrameworkVersion = "v9.0", ManifestFile = CreateManiestFile (10, 27), }; Assert.True (task.Execute (), "Task should have succeeded."); @@ -75,7 +73,6 @@ public void CheckManifestCompileSdkLowerThanTargetSdk () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, - TargetFrameworkVersion = "v8.1", ManifestFile = CreateManiestFile (10, 28), }; Assert.True (task.Execute (), "Task should have succeeded."); @@ -88,7 +85,6 @@ public void CheckManifestMinSdkLowerThanTargetSdk () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, - TargetFrameworkVersion = "v8.1", ManifestFile = CreateManiestFile (28, 27), }; Assert.True (task.Execute (), "Task should have succeeded."); From 3b70a5e4faecabbfc09ca98d6de19f59a09939a5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Oct 2025 16:04:32 -0500 Subject: [PATCH 04/17] Fix `` --- .../Tasks/GenerateMainAndroidManifest.cs | 11 +++++++++-- .../Xamarin.Android.Common.targets | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs index 9d9782f64ce..dcece671899 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateMainAndroidManifest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Microsoft.Android.Build.Tasks; using Microsoft.Build.Framework; @@ -19,7 +20,8 @@ public class GenerateMainAndroidManifest : AndroidTask [Required] public string AndroidRuntime { get; set; } = ""; public string? AndroidSdkDir { get; set; } - public string? AndroidSdkPlatform { get; set; } + [Required] + public string AndroidApiLevel { get; set; } = ""; public string? ApplicationJavaClass { get; set; } public string? ApplicationLabel { get; set; } public string? BundledWearApplicationName { get; set; } @@ -91,6 +93,11 @@ public override bool RunTask () IList MergeManifest (NativeCodeGenState codeGenState, Dictionary userAssemblies) { + string targetSdkVersion = AndroidApiLevel; + if (MonoAndroidHelper.TryParseApiLevel (targetSdkVersion, out Version version)) { + targetSdkVersion = version.Major.ToString (CultureInfo.InvariantCulture); + } + var manifest = new ManifestDocument (ManifestTemplate) { PackageName = PackageName, VersionName = VersionName, @@ -98,7 +105,7 @@ IList MergeManifest (NativeCodeGenState codeGenState, Dictionary Date: Tue, 14 Oct 2025 16:07:20 -0500 Subject: [PATCH 05/17] Remove `AndroidSdkPlatform` from `` --- src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs | 4 +--- src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs | 5 ++--- .../Xamarin.Android.Common.targets | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs index 86d7da7b765..bf709baf4c6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs @@ -47,8 +47,6 @@ public class GenerateJavaStubs : AndroidTask public bool Debug { get; set; } - [Required] - public string AndroidSdkPlatform { get; set; } = ""; [Required] public string OutputDirectory { get; set; } = ""; @@ -237,7 +235,7 @@ internal static Dictionary MaybeGetArchAssemblies (Dictionary bool success = true; if (generateJavaCode && RunCheckedBuild) { - success = jcwGenerator.Generate (AndroidSdkPlatform, outputPath: Path.Combine (OutputDirectory, "src"), ApplicationJavaClass); + success = jcwGenerator.Generate (outputPath: Path.Combine (OutputDirectory, "src"), ApplicationJavaClass); generatedJavaFiles = jcwGenerator.GeneratedJavaFiles; } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs index d466ab85b64..3bb8c5a6ee4 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/JCWGenerator.cs @@ -53,17 +53,16 @@ public JCWGenerator (TaskLoggingHelper log, JCWGeneratorContext context) public List GeneratedJavaFiles { get; } = []; - public bool Generate (string androidSdkPlatform, string outputPath, string applicationJavaClass) + public bool Generate (string outputPath, string applicationJavaClass) { return ProcessTypes ( generateCode: true, - androidSdkPlatform, outputPath, applicationJavaClass ); } - bool ProcessTypes (bool generateCode, string androidSdkPlatform, string? outputPath, string? applicationJavaClass) + bool ProcessTypes (bool generateCode, string? outputPath, string? applicationJavaClass) { if (generateCode && outputPath.IsNullOrEmpty ()) { throw new ArgumentException ("must not be null or empty", nameof (outputPath)); diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index 7ef4ee0ce43..f52d9f1c47e 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1590,7 +1590,6 @@ because xbuild doesn't support framework reference assemblies. GeneratedJavaFiles="@(_GeneratedJavaFiles)" ErrorOnCustomJavaObject="$(AndroidErrorOnCustomJavaObject)" Debug="$(AndroidIncludeDebugSymbols)" - AndroidSdkPlatform="$(_AndroidApiLevel)" OutputDirectory="$(IntermediateOutputPath)android" PackageNamingPolicy="$(AndroidPackageNamingPolicy)" ApplicationJavaClass="$(AndroidApplicationJavaClass)" From 7de25aba7643651ca12d47fda7460ab650d250fc Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Oct 2025 16:08:28 -0500 Subject: [PATCH 06/17] Update --- .../Tasks/GetJavaPlatformJar.cs | 8 ++++---- .../Xamarin.Android.Tooling.targets | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs index ae82bde9f2e..22af704d084 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs @@ -21,7 +21,7 @@ public class GetJavaPlatformJar : AndroidTask private XNamespace androidNs = "http://schemas.android.com/apk/res/android"; [Required] - public string AndroidSdkPlatform { get; set; } = ""; + public string AndroidApiLevel { get; set; } = ""; public string? AndroidManifest { get; set; } @@ -45,7 +45,7 @@ public class GetJavaPlatformJar : AndroidTask public override bool RunTask () { - var platform = AndroidSdkPlatform; + var platform = AndroidApiLevel; XAttribute? target_sdk = null; @@ -117,14 +117,14 @@ public override bool RunTask () string GetTargetSdkVersion (string target, XAttribute? target_sdk) { - string targetFrameworkVersion = MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (AndroidSdkPlatform) ?? ""; + string targetFrameworkVersion = MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (AndroidApiLevel) ?? ""; string targetSdkVersion = MonoAndroidHelper.SupportedVersions.GetIdFromApiLevel (target) ?? ""; // For .NET 6+ projects, use TargetPlatformVersion directly string targetPlatformVersionDisplay = !TargetPlatformVersion.IsNullOrEmpty () ? TargetPlatformVersion : ""; if (!int.TryParse (targetFrameworkVersion, out int frameworkSdk)) { - // AndroidSdkPlatform is likely a *preview* API level; use it. + // AndroidApiLevel is likely a *preview* API level; use it. Log.LogWarningForXmlNode ( code: "XA4211", file: AndroidManifest, diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets index a6b7ff72ed9..cafc18be2cf 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Tooling.targets @@ -86,7 +86,7 @@ projects. Date: Tue, 14 Oct 2025 16:10:26 -0500 Subject: [PATCH 07/17] Fix --- .../Xamarin/Android/Xamarin.Android.Aapt2.targets | 2 +- src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets index 497e0c0bbb6..f6c82196107 100644 --- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets +++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets @@ -232,7 +232,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved. CreatePackagePerAbi="$(AndroidCreatePackagePerAbi)" AssetsDirectory="$(MonoAndroidAssetsDirIntermediate)" AdditionalAndroidAssetPaths="@(LibraryAssetDirectories)" - AndroidSdkPlatform="$(_AndroidApiLevel)" + AndroidApiLevel="$(_AndroidApiLevel)" JavaDesignerOutputDirectory="$(AaptTemporaryDirectory)" ManifestFiles="$(IntermediateOutputPath)android\AndroidManifest.xml" ProtobufFormat="$(_ProtobufFormat)" diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs index b731323fd1d..f37e8d70223 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs @@ -2,19 +2,16 @@ #nullable enable using System; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text; -using System.Threading; -using System.Xml; -using System.Xml.Linq; using Microsoft.Build.Utilities; using Microsoft.Build.Framework; using System.Text.RegularExpressions; using System.Collections.Generic; using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; +using System.Globalization; namespace Xamarin.Android.Tasks { @@ -61,7 +58,8 @@ public class Aapt2Link : Aapt2 { public string? UncompressedFileExtensions { get; set; } - public string? AndroidSdkPlatform { get; set; } + [Required] + public string AndroidApiLevel { get; set; } = ""; public string? VersionCodePattern { get; set; } @@ -154,8 +152,12 @@ string [] GenerateCommandLineCommands (string ManifestFile, string? currentAbi, string manifestDir = Path.Combine (Path.GetDirectoryName (ManifestFile), currentAbi != null ? currentAbi : "manifest"); Directory.CreateDirectory (manifestDir); string manifestFile = Path.Combine (manifestDir, Path.GetFileName (ManifestFile)); + string targetSdkVersion = AndroidApiLevel; + if (MonoAndroidHelper.TryParseApiLevel (targetSdkVersion, out Version version)) { + targetSdkVersion = version.Major.ToString (CultureInfo.InvariantCulture); + } ManifestDocument manifest = new ManifestDocument (ManifestFile); - manifest.TargetSdkVersion = AndroidSdkPlatform; + manifest.TargetSdkVersion = targetSdkVersion; if (!VersionCodePattern.IsNullOrEmpty ()) { try { manifest.CalculateVersionCode (currentAbi, VersionCodePattern, VersionCodeProperties); From a78c972e77934e6d40e41b583cca4ea69f108096 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 15 Oct 2025 08:21:04 -0500 Subject: [PATCH 08/17] Update Aapt2Link.cs --- src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs index f37e8d70223..000c0482b4a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs @@ -58,7 +58,6 @@ public class Aapt2Link : Aapt2 { public string? UncompressedFileExtensions { get; set; } - [Required] public string AndroidApiLevel { get; set; } = ""; public string? VersionCodePattern { get; set; } From d8b741a35e6697da9dddaa9ac2163f7dbb5c579a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 15 Oct 2025 10:15:45 -0500 Subject: [PATCH 09/17] Fix `` --- .../Tasks/ReadAndroidManifest.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ReadAndroidManifest.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ReadAndroidManifest.cs index 1d2d6392dba..9a31cd7487e 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ReadAndroidManifest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ReadAndroidManifest.cs @@ -5,6 +5,8 @@ using System.IO; using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; +using System; +using System.Globalization; namespace Xamarin.Android.Tasks { @@ -66,7 +68,17 @@ public override bool RunTask () var attribute = uses_library.Attribute (androidNs + "name"); if (attribute != null && !attribute.Value.IsNullOrEmpty ()) { var required = uses_library.Attribute (androidNs + "required")?.Value; - var path = Path.Combine (AndroidSdkDirectory, "platforms", $"android-{AndroidApiLevel}", "optional", $"{attribute.Value}.jar"); + string apiLevel; + if (MonoAndroidHelper.TryParseApiLevel (AndroidApiLevel, out Version version)) { + if (version.Minor == 0) { + apiLevel = version.Major.ToString (CultureInfo.InvariantCulture); + } else { + apiLevel = version.ToString (); + } + } else { + apiLevel = AndroidApiLevel; + } + var path = Path.Combine (AndroidSdkDirectory, "platforms", $"android-{apiLevel}", "optional", $"{attribute.Value}.jar"); if (File.Exists (path)) { libraries.Add (new TaskItem (path)); } else { From 5618e9243b95e9d5229f0f9be2938f130621848e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 15 Oct 2025 10:16:38 -0500 Subject: [PATCH 10/17] Update ResolveAndroidTooling.cs --- .../Tasks/ResolveAndroidTooling.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs index a805675e21d..e01cbbbebd1 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveAndroidTooling.cs @@ -81,7 +81,11 @@ public override bool RunTask () { // This should be 31.0, 32.0, etc. if (Version.TryParse (TargetPlatformVersion, out Version v)) { - AndroidApiLevel = v.ToString (); + if (v.Minor == 0) { + AndroidApiLevel = v.Major.ToString (CultureInfo.InvariantCulture); + } else { + AndroidApiLevel = v.ToString (); + } } else { AndroidApiLevel = GetMaxStableApiLevel ().ToString (); } From 1457aeaa91072916ac9cc7e52ba56b45a8e57219 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Oct 2025 08:47:05 -0500 Subject: [PATCH 11/17] Fix CheckGoogleSdkRequirementsTests --- .../Tasks/CheckGoogleSdkRequirementsTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs index e85bb3e070b..4948f9b733a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/CheckGoogleSdkRequirementsTests.cs @@ -49,6 +49,7 @@ public void CheckManifestIsOK () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, + AndroidApiLevel = "28", ManifestFile = CreateManiestFile (10, 28), }; Assert.True (task.Execute (), "Task should have succeeded."); @@ -61,6 +62,7 @@ public void CheckManifestTargetSdkLowerThanCompileSdk () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, + AndroidApiLevel = "28", ManifestFile = CreateManiestFile (10, 27), }; Assert.True (task.Execute (), "Task should have succeeded."); @@ -73,11 +75,12 @@ public void CheckManifestCompileSdkLowerThanTargetSdk () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, + AndroidApiLevel = "27", ManifestFile = CreateManiestFile (10, 28), }; Assert.True (task.Execute (), "Task should have succeeded."); Assert.AreEqual (0, errors.Count, "There should be 1 error reported."); - Assert.AreEqual (1, warnings.Count, "There should be 0 warnings reported."); + Assert.AreEqual (1, warnings.Count, "There should be 1 warning reported."); } [Test] @@ -85,11 +88,12 @@ public void CheckManifestMinSdkLowerThanTargetSdk () { var task = new CheckGoogleSdkRequirements () { BuildEngine = engine, + AndroidApiLevel = "27", ManifestFile = CreateManiestFile (28, 27), }; Assert.True (task.Execute (), "Task should have succeeded."); Assert.AreEqual (0, errors.Count, "There should be 0 error reported."); - Assert.AreEqual (1, warnings.Count, "There should be 1 warnings reported."); + Assert.AreEqual (1, warnings.Count, "There should be 1 warning reported."); } } } From bc0e1ae3f2d428045473a119cdecaca27445efeb Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Oct 2025 08:50:15 -0500 Subject: [PATCH 12/17] Update GetJavaPlatformJar.cs --- src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs index 22af704d084..5f57171f1b2 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs @@ -123,7 +123,7 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) // For .NET 6+ projects, use TargetPlatformVersion directly string targetPlatformVersionDisplay = !TargetPlatformVersion.IsNullOrEmpty () ? TargetPlatformVersion : ""; - if (!int.TryParse (targetFrameworkVersion, out int frameworkSdk)) { + if (!Version.TryParse (targetFrameworkVersion, out var frameworkSdk)) { // AndroidApiLevel is likely a *preview* API level; use it. Log.LogWarningForXmlNode ( code: "XA4211", @@ -138,7 +138,7 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) ); return targetFrameworkVersion; } - if (int.TryParse (targetSdkVersion, out int targetSdk) && + if (Version.TryParse (targetSdkVersion, out var targetSdk) && targetSdk < frameworkSdk) { Log.LogWarningForXmlNode ( code: "XA4211", From 81a3b5eacf86c9e90a062ab83452d3a6e978559a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Oct 2025 13:14:10 -0500 Subject: [PATCH 13/17] Update GetJavaPlatformJar.cs --- src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs index 5f57171f1b2..9f36138185a 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs @@ -139,7 +139,7 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) return targetFrameworkVersion; } if (Version.TryParse (targetSdkVersion, out var targetSdk) && - targetSdk < frameworkSdk) { + targetSdk.Major < frameworkSdk.Major) { Log.LogWarningForXmlNode ( code: "XA4211", file: AndroidManifest, From 39fc98aed96a8c34687015f482a45ee67c73450a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 17 Oct 2025 08:57:59 -0500 Subject: [PATCH 14/17] Update GetJavaPlatformJar.cs --- src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs index 9f36138185a..8e6dfa519f3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs @@ -123,7 +123,7 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) // For .NET 6+ projects, use TargetPlatformVersion directly string targetPlatformVersionDisplay = !TargetPlatformVersion.IsNullOrEmpty () ? TargetPlatformVersion : ""; - if (!Version.TryParse (targetFrameworkVersion, out var frameworkSdk)) { + if (!MonoAndroidHelper.TryParseApiLevel (targetFrameworkVersion, out var frameworkSdk)) { // AndroidApiLevel is likely a *preview* API level; use it. Log.LogWarningForXmlNode ( code: "XA4211", @@ -138,7 +138,7 @@ string GetTargetSdkVersion (string target, XAttribute? target_sdk) ); return targetFrameworkVersion; } - if (Version.TryParse (targetSdkVersion, out var targetSdk) && + if (MonoAndroidHelper.TryParseApiLevel (targetSdkVersion, out var targetSdk) && targetSdk.Major < frameworkSdk.Major) { Log.LogWarningForXmlNode ( code: "XA4211", From f972469b754e099a0b9fe7699ba613c92180d723 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 17 Oct 2025 12:53:47 -0500 Subject: [PATCH 15/17] Merged tests --- .../Xamarin.Android.Build.Tests/BuildTest2.cs | 27 ------------------- .../Tests/InstallAndRunTests.cs | 10 +++++++ 2 files changed, 10 insertions(+), 27 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 8b40cece5bd..6e8c70e7d1d 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 @@ -1744,32 +1744,5 @@ public void Plugin_Maui_Audio () const string className = "Lcrc64467b05f37239e7a6/StreamMediaDataSource;"; Assert.IsTrue (DexUtils.ContainsClass (className, dexFile, AndroidSdkPath), $"`{dexFile}` should include `{className}`!"); } - - [Test] - public void SubclassMinorApiLevels ([Values ("net10.0-android36.1")] string targetFramework) - { - var proj = new XamarinAndroidApplicationProject () { - TargetFramework = targetFramework, - ExtraNuGetConfigSources = { - Path.Combine (XABuildPaths.BuildOutputDirectory, "nuget-unsigned"), - } - }; - - // TODO: update on new minor API levels to use an introduced minor API - proj.MainActivity = proj.DefaultMainActivity - .Replace ("//${USINGS}", "using Android.Graphics.Pdf.Component;") - .Replace ("//${AFTER_MAINACTIVITY}", """ - class MyTextObjectFont : PdfPageTextObjectFont - { - public MyTextObjectFont (PdfPageTextObjectFont font) : base (font) - { - } - } - """); - - - var builder = CreateApkBuilder (); - Assert.IsTrue (builder.Build (proj), "`dotnet build` should succeed"); - } } } diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 75518adace2..7d9d16252af 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -970,6 +970,16 @@ public void DotNetInstallAndRunMinorAPILevels ( } else { Console.WriteLine ("TelecomManager.ActionCallBack not available"); } + """) + .Replace ("//${AFTER_MAINACTIVITY}", """ + #pragma warning disable CA1416 // Type only available on Android 36.1 and later + class MyTextObjectFont : PdfPageTextObjectFont + { + public MyTextObjectFont (PdfPageTextObjectFont font) : base (font) + { + } + } + #pragma warning restore CA1416 // Type only available on Android 36.1 and later """); var builder = CreateApkBuilder (); From aeb2e305d38e12c1d931f8d91233d17fd784775c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 17 Oct 2025 13:21:37 -0500 Subject: [PATCH 16/17] Fix merge --- src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs index fc2a4a819f3..7bdaf4c8589 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using Xamarin.Android.Tools; using Microsoft.Android.Build.Tasks; -using System.Globalization; namespace Xamarin.Android.Tasks { @@ -153,8 +152,8 @@ string [] GenerateCommandLineCommands (string ManifestFile, string? currentAbi, Directory.CreateDirectory (manifestDir); string manifestFile = Path.Combine (manifestDir, Path.GetFileName (ManifestFile)); string targetSdkVersion = AndroidApiLevel; - if (MonoAndroidHelper.TryParseApiLevel (targetSdkVersion, out Version version)) { - targetSdkVersion = version.Major.ToString (CultureInfo.InvariantCulture); + if (MonoAndroidHelper.TryParseApiLevel (targetSdkVersion, out Version v)) { + targetSdkVersion = v.Major.ToString (CultureInfo.InvariantCulture); } ManifestDocument manifest = new ManifestDocument (ManifestFile); manifest.TargetSdkVersion = targetSdkVersion; From b73c4aa604d4ca3a1818659aa4070be4bb42729e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 17 Oct 2025 14:24:46 -0500 Subject: [PATCH 17/17] Update InstallAndRunTests.cs --- tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 7d9d16252af..58a72be277a 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -963,7 +963,7 @@ public void DotNetInstallAndRunMinorAPILevels ( // TODO: update on new minor API levels to use an introduced minor API proj.MainActivity = proj.DefaultMainActivity - .Replace ("//${USINGS}", "using Android.Telecom;") + .Replace ("//${USINGS}", "using Android.Telecom;\nusing Android.Graphics.Pdf.Component;") .Replace ("//${AFTER_ONCREATE}", """ if (OperatingSystem.IsAndroidVersionAtLeast (36, 1)) { Console.WriteLine ($"TelecomManager.ActionCallBack={TelecomManager.ActionCallBack}");