diff --git a/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml b/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml
index aa27d4dc9a4..e4f6c6a5564 100644
--- a/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml
+++ b/build-tools/automation/yaml-templates/stage-msbuild-emulator-tests.yaml
@@ -49,6 +49,14 @@ stages:
artifactName: $(TestAssembliesArtifactName)
downloadPath: ${{ parameters.xaSourcePath }}/bin/Test$(XA.Build.Configuration)
+ # Currently needed for samples/NativeAOT
+ - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self
+ parameters:
+ project: Xamarin.Android.sln
+ arguments: -t:PrepareJavaInterop -c $(XA.Build.Configuration) --no-restore
+ displayName: prepare java.interop $(XA.Build.Configuration)
+ continueOnError: false
+
- template: /build-tools/automation/yaml-templates/start-stop-emulator.yaml
parameters:
xaSourcePath: ${{ parameters.xaSourcePath }}
diff --git a/samples/NativeAOT/AndroidManifest.xml b/samples/NativeAOT/AndroidManifest.xml
new file mode 100644
index 00000000000..70e89c99003
--- /dev/null
+++ b/samples/NativeAOT/AndroidManifest.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/NativeAOT/JavaInteropRuntime.cs b/samples/NativeAOT/JavaInteropRuntime.cs
new file mode 100644
index 00000000000..4e54afeb3ba
--- /dev/null
+++ b/samples/NativeAOT/JavaInteropRuntime.cs
@@ -0,0 +1,54 @@
+using Android.Runtime;
+using Java.Interop;
+using System.Runtime.InteropServices;
+
+namespace NativeAOT;
+
+static class JavaInteropRuntime
+{
+ static JniRuntime? runtime;
+
+ [UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
+ static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
+ {
+ try {
+ AndroidLog.Print (AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnLoad()");
+ LogcatTextWriter.Init ();
+ return (int) JniVersion.v1_6;
+ }
+ catch (Exception e) {
+ AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JNI_OnLoad() failed: {e}");
+ return 0;
+ }
+ }
+
+ [UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")]
+ static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
+ {
+ AndroidLog.Print(AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnUnload");
+ runtime?.Dispose ();
+ }
+
+ // symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
+ [UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")]
+ static void init (IntPtr jnienv, IntPtr klass)
+ {
+ try {
+ var options = new JreRuntimeOptions {
+ EnvironmentPointer = jnienv,
+ TypeManager = new NativeAotTypeManager (),
+ ValueManager = new NativeAotValueManager (),
+ UseMarshalMemberBuilder = false,
+ JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"),
+ JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"),
+ };
+ runtime = options.CreateJreVM ();
+
+ // Entry point into Mono.Android.dll
+ JNIEnvInit.InitializeJniRuntime (runtime);
+ }
+ catch (Exception e) {
+ AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JavaInteropRuntime.init: error: {e}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/NativeAOT/JavaInteropRuntime.java b/samples/NativeAOT/JavaInteropRuntime.java
new file mode 100644
index 00000000000..1eb07e9f2e5
--- /dev/null
+++ b/samples/NativeAOT/JavaInteropRuntime.java
@@ -0,0 +1,15 @@
+package net.dot.jni.nativeaot;
+
+import android.util.Log;
+
+public class JavaInteropRuntime {
+ static {
+ Log.d("JavaInteropRuntime", "Loading NativeAOT.so...");
+ System.loadLibrary("NativeAOT");
+ }
+
+ private JavaInteropRuntime() {
+ }
+
+ public static native void init();
+}
diff --git a/samples/NativeAOT/Logging.cs b/samples/NativeAOT/Logging.cs
new file mode 100644
index 00000000000..5073ba9e5a3
--- /dev/null
+++ b/samples/NativeAOT/Logging.cs
@@ -0,0 +1,77 @@
+// NOTE: logging methods below are need temporarily due to:
+// 1) linux-bionic BCL doesn't redirect stdout/stderr to logcat
+// 2) Android.Util.Log won't work until we initialize the Java.Interop.JreRuntime
+
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace NativeAOT;
+
+internal sealed class LogcatTextWriter : TextWriter {
+
+ public static void Init ()
+ {
+ // This method is a no-op, but it's necessary to ensure the static
+ // constructor is executed.
+ }
+
+ static LogcatTextWriter ()
+ {
+ Console.SetOut (new LogcatTextWriter (AndroidLogLevel.Info));
+ Console.SetError (new LogcatTextWriter (AndroidLogLevel.Error));
+ }
+
+ AndroidLogLevel Level;
+ string Tag;
+
+ internal LogcatTextWriter (AndroidLogLevel level, string tag = "NativeAotFromAndroid")
+ {
+ Level = level;
+ Tag = tag;
+ }
+
+ public override Encoding Encoding => Encoding.UTF8;
+ public override string NewLine => "\n";
+
+ public override void WriteLine (string? value)
+ {
+ if (value == null) {
+ AndroidLog.Print (Level, Tag, "");
+ return;
+ }
+ ReadOnlySpan span = value;
+ while (!span.IsEmpty) {
+ if (span.IndexOf ('\n') is int n && n < 0) {
+ break;
+ }
+ var line = span.Slice (0, n);
+ AndroidLog.Print (Level, Tag, line.ToString ());
+ span = span.Slice (n + 1);
+ }
+ AndroidLog.Print (Level, Tag, span.ToString ());
+ }
+}
+
+static class AndroidLog {
+
+ [DllImport ("log", EntryPoint = "__android_log_print", CallingConvention = CallingConvention.Cdecl)]
+ private static extern void __android_log_print(AndroidLogLevel level, string? tag, string format, string args, IntPtr ptr);
+
+ internal static void Print(AndroidLogLevel level, string? tag, string message) =>
+ __android_log_print(level, tag, "%s", message, IntPtr.Zero);
+
+}
+
+internal enum AndroidLogLevel
+{
+ Unknown = 0x00,
+ Default = 0x01,
+ Verbose = 0x02,
+ Debug = 0x03,
+ Info = 0x04,
+ Warn = 0x05,
+ Error = 0x06,
+ Fatal = 0x07,
+ Silent = 0x08
+}
\ No newline at end of file
diff --git a/samples/NativeAOT/MainActivity.cs b/samples/NativeAOT/MainActivity.cs
new file mode 100644
index 00000000000..545983d62fe
--- /dev/null
+++ b/samples/NativeAOT/MainActivity.cs
@@ -0,0 +1,18 @@
+using Android.Runtime;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace NativeAOT;
+
+[Register("my/MainActivity")] // Required for typemap in NativeAotTypeManager
+[Activity(Label = "@string/app_name", MainLauncher = true)]
+public class MainActivity : Activity
+{
+ protected override void OnCreate(Bundle? savedInstanceState)
+ {
+ base.OnCreate(savedInstanceState);
+
+ // Set our view from the "main" layout resource
+ SetContentView(Resource.Layout.activity_main);
+ }
+}
\ No newline at end of file
diff --git a/samples/NativeAOT/NativeAOT.csproj b/samples/NativeAOT/NativeAOT.csproj
new file mode 100644
index 00000000000..e392eaa7646
--- /dev/null
+++ b/samples/NativeAOT/NativeAOT.csproj
@@ -0,0 +1,36 @@
+
+
+ $(DotNetAndroidTargetFramework)
+ 21
+ Exe
+ enable
+ enable
+ net.dot.hellonativeaot
+ 1
+ 1.0
+ apk
+ true
+
+ true
+ ..\..\product.snk
+
+ android-arm64
+
+ true
+ true
+
+
+
+
+
+ android-x64
+ <_NuGetFolderOnCI>..\..\bin\Build$(Configuration)\nuget-unsigned
+ $(_NuGetFolderOnCI)
+ <_FastDeploymentDiagnosticLogging>true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/NativeAOT/NativeAotRuntimeProvider.java b/samples/NativeAOT/NativeAotRuntimeProvider.java
new file mode 100644
index 00000000000..b87f23f25c7
--- /dev/null
+++ b/samples/NativeAOT/NativeAotRuntimeProvider.java
@@ -0,0 +1,51 @@
+package net.dot.jni.nativeaot;
+
+import android.util.Log;
+
+public class NativeAotRuntimeProvider
+ extends android.content.ContentProvider
+{
+ private static final String TAG = "NativeAotRuntimeProvider";
+
+ public NativeAotRuntimeProvider() {
+ Log.d(TAG, "NativeAotRuntimeProvider()");
+ }
+
+ @Override
+ public boolean onCreate() {
+ Log.d(TAG, "NativeAotRuntimeProvider.onCreate()");
+ return true;
+ }
+
+ @Override
+ public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) {
+ Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…");
+ JavaInteropRuntime.init();
+ super.attachInfo (context, info);
+ }
+
+ @Override
+ public android.database.Cursor query(android.net.Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public String getType(android.net.Uri uri) {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public android.net.Uri insert(android.net.Uri uri, android.content.ContentValues initialValues) {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public int delete(android.net.Uri uri, String where, String[] whereArgs) {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+
+ @Override
+ public int update(android.net.Uri uri, android.content.ContentValues values, String where, String[] whereArgs) {
+ throw new RuntimeException ("This operation is not supported.");
+ }
+}
\ No newline at end of file
diff --git a/samples/NativeAOT/NativeAotTypeManager.cs b/samples/NativeAOT/NativeAotTypeManager.cs
new file mode 100644
index 00000000000..29499d17847
--- /dev/null
+++ b/samples/NativeAOT/NativeAotTypeManager.cs
@@ -0,0 +1,164 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Java.Interop;
+using Java.Interop.Tools.TypeNameMappings;
+
+namespace NativeAOT;
+
+partial class NativeAotTypeManager : JniRuntime.JniTypeManager {
+
+ internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
+ internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;
+
+ // TODO: list of types specific to this application
+ Dictionary typeMappings = new () {
+ ["android/app/Activity"] = typeof (Android.App.Activity),
+ ["android/content/Context"] = typeof (Android.Content.Context),
+ ["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper),
+ ["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle),
+ ["android/os/Bundle"] = typeof (Android.OS.Bundle),
+ ["android/view/ContextThemeWrapper"] = typeof (Android.Views.ContextThemeWrapper),
+ ["my/MainActivity"] = typeof (MainActivity),
+ };
+
+ public NativeAotTypeManager ()
+ {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: NativeAotTypeManager()");
+ }
+
+ public override void RegisterNativeMembers (
+ JniType nativeClass,
+ [DynamicallyAccessedMembers (MethodsAndPrivateNested)]
+ Type type,
+ ReadOnlySpan methods)
+ {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: RegisterNativeMembers: nativeClass={nativeClass} type=`{type}`");
+
+ if (methods.IsEmpty) {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", "methods.IsEmpty");
+ return;
+ }
+
+ int methodCount = CountMethods (methods);
+ if (methodCount < 1) {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"methodCount < 1: {methodCount}");
+ return;
+ }
+
+ JniNativeMethodRegistration [] natives = new JniNativeMethodRegistration [methodCount];
+ int nativesIndex = 0;
+
+ ReadOnlySpan methodsSpan = methods;
+ bool needToRegisterNatives = false;
+
+ while (!methodsSpan.IsEmpty) {
+ int newLineIndex = methodsSpan.IndexOf ('\n');
+
+ ReadOnlySpan methodLine = methodsSpan.Slice (0, newLineIndex != -1 ? newLineIndex : methodsSpan.Length);
+ if (!methodLine.IsEmpty) {
+ SplitMethodLine (methodLine,
+ out ReadOnlySpan name,
+ out ReadOnlySpan signature,
+ out ReadOnlySpan callbackString,
+ out ReadOnlySpan callbackDeclaringTypeString);
+
+ Delegate? callback = null;
+ if (callbackString.SequenceEqual ("__export__")) {
+ throw new InvalidOperationException (FormattableString.Invariant ($"Methods such as {callbackString.ToString ()} are not implemented!"));
+ } else {
+ Type callbackDeclaringType = type;
+ if (!callbackDeclaringTypeString.IsEmpty) {
+ callbackDeclaringType = Type.GetType (callbackDeclaringTypeString.ToString (), throwOnError: true)!;
+ }
+ while (callbackDeclaringType.ContainsGenericParameters) {
+ callbackDeclaringType = callbackDeclaringType.BaseType!;
+ }
+
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: Delegate.CreateDelegate callbackDeclaringType={callbackDeclaringType}, callbackString={callbackString}");
+ GetCallbackHandler connector = (GetCallbackHandler) Delegate.CreateDelegate (typeof (GetCallbackHandler),
+ callbackDeclaringType, callbackString.ToString ());
+ callback = connector ();
+ }
+
+ if (callback != null) {
+ needToRegisterNatives = true;
+ natives [nativesIndex++] = new JniNativeMethodRegistration (name.ToString (), signature.ToString (), callback);
+ }
+ }
+
+ methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default;
+ }
+
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: needToRegisterNatives={needToRegisterNatives}");
+
+ if (needToRegisterNatives) {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: RegisterNatives: nativeClass={nativeClass} type=`{type}` natives={natives.Length} nativesIndex={nativesIndex}");
+ JniEnvironment.Types.RegisterNatives (nativeClass.PeerReference, natives, nativesIndex);
+ }
+ }
+
+
+ protected override IEnumerable GetTypesForSimpleReference (string jniSimpleReference)
+ {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}`");
+ if (typeMappings.TryGetValue (jniSimpleReference, out var target)) {
+ Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{target}`");
+ yield return target;
+ }
+ foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) {
+ AndroidLog.Print (AndroidLogLevel.Info, "NativeAotTypeManager", $"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{t}`");
+ yield return t;
+ }
+ }
+
+ protected override IEnumerable GetSimpleReferences (Type type)
+ {
+ return base.GetSimpleReferences (type)
+ .Concat (CreateSimpleReferencesEnumerator (type));
+ }
+
+ IEnumerable CreateSimpleReferencesEnumerator (Type type)
+ {
+ if (typeMappings == null)
+ yield break;
+ foreach (var e in typeMappings) {
+ if (e.Value == type)
+ yield return e.Key;
+ }
+ }
+
+ static int CountMethods (ReadOnlySpan methodsSpan)
+ {
+ int count = 0;
+ while (!methodsSpan.IsEmpty) {
+ count++;
+
+ int newLineIndex = methodsSpan.IndexOf ('\n');
+ methodsSpan = newLineIndex != -1 ? methodsSpan.Slice (newLineIndex + 1) : default;
+ }
+ return count;
+ }
+
+ static void SplitMethodLine (
+ ReadOnlySpan methodLine,
+ out ReadOnlySpan name,
+ out ReadOnlySpan signature,
+ out ReadOnlySpan callback,
+ out ReadOnlySpan callbackDeclaringType)
+ {
+ int colonIndex = methodLine.IndexOf (':');
+ name = methodLine.Slice (0, colonIndex);
+ methodLine = methodLine.Slice (colonIndex + 1);
+
+ colonIndex = methodLine.IndexOf (':');
+ signature = methodLine.Slice (0, colonIndex);
+ methodLine = methodLine.Slice (colonIndex + 1);
+
+ colonIndex = methodLine.IndexOf (':');
+ callback = methodLine.Slice (0, colonIndex != -1 ? colonIndex : methodLine.Length);
+
+ callbackDeclaringType = colonIndex != -1 ? methodLine.Slice (colonIndex + 1) : default;
+ }
+
+ delegate Delegate GetCallbackHandler ();
+}
diff --git a/samples/NativeAOT/NativeAotValueManager.cs b/samples/NativeAOT/NativeAotValueManager.cs
new file mode 100644
index 00000000000..8fb2c55d4e2
--- /dev/null
+++ b/samples/NativeAOT/NativeAotValueManager.cs
@@ -0,0 +1,10 @@
+using Android.Runtime;
+
+namespace NativeAOT;
+
+// TODO: make a NativeAOT-specific implementation of AndroidValueManager
+// This is enough for "Hello World" to launch
+internal class NativeAotValueManager : AndroidValueManager
+{
+
+}
diff --git a/samples/NativeAOT/Resources/layout/activity_main.xml b/samples/NativeAOT/Resources/layout/activity_main.xml
new file mode 100644
index 00000000000..f94985291bf
--- /dev/null
+++ b/samples/NativeAOT/Resources/layout/activity_main.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon.xml b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon.xml
new file mode 100644
index 00000000000..7751f69514d
--- /dev/null
+++ b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon_round.xml b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon_round.xml
new file mode 100644
index 00000000000..7751f69514d
--- /dev/null
+++ b/samples/NativeAOT/Resources/mipmap-anydpi-v26/appicon_round.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/samples/NativeAOT/Resources/mipmap-hdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-hdpi/appicon.png
new file mode 100644
index 00000000000..0abfc1b581c
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-hdpi/appicon.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-hdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_background.png
new file mode 100644
index 00000000000..513e69d8412
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_background.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-hdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_foreground.png
new file mode 100644
index 00000000000..99d3a291bc3
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-hdpi/appicon_foreground.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-mdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-mdpi/appicon.png
new file mode 100644
index 00000000000..7b5a2e2bf8d
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-mdpi/appicon.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-mdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_background.png
new file mode 100644
index 00000000000..9e2d1e4d8d4
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_background.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-mdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_foreground.png
new file mode 100644
index 00000000000..a28d342c1c7
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-mdpi/appicon_foreground.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xhdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon.png
new file mode 100644
index 00000000000..b28b73c6545
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_background.png
new file mode 100644
index 00000000000..658be3fb6a3
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_background.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_foreground.png
new file mode 100644
index 00000000000..70a542ac03c
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xhdpi/appicon_foreground.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon.png
new file mode 100644
index 00000000000..f9af1173ead
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_background.png
new file mode 100644
index 00000000000..9171c3e4044
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_background.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_foreground.png
new file mode 100644
index 00000000000..cb63bfb9f8e
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxhdpi/appicon_foreground.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon.png b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon.png
new file mode 100644
index 00000000000..1d948d6b5d8
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_background.png b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_background.png
new file mode 100644
index 00000000000..1232d8c8d23
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_background.png differ
diff --git a/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_foreground.png b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_foreground.png
new file mode 100644
index 00000000000..9f9c9e6d932
Binary files /dev/null and b/samples/NativeAOT/Resources/mipmap-xxxhdpi/appicon_foreground.png differ
diff --git a/samples/NativeAOT/Resources/values/ic_launcher_background.xml b/samples/NativeAOT/Resources/values/ic_launcher_background.xml
new file mode 100644
index 00000000000..6ec24e6413c
--- /dev/null
+++ b/samples/NativeAOT/Resources/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #2C3E50
+
\ No newline at end of file
diff --git a/samples/NativeAOT/Resources/values/strings.xml b/samples/NativeAOT/Resources/values/strings.xml
new file mode 100644
index 00000000000..8d13608953c
--- /dev/null
+++ b/samples/NativeAOT/Resources/values/strings.xml
@@ -0,0 +1,4 @@
+
+ HelloNativeAOT
+ Hello, NativeAOT on Android!
+
diff --git a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
index 19ff11a0913..cb1f39a8ec5 100644
--- a/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
+++ b/src/Mono.Android/Android.Runtime/JNIEnvInit.cs
@@ -48,7 +48,7 @@ internal struct JnienvInitializeArgs {
internal static IntPtr java_class_loader;
internal static JniMethodInfo? mid_Class_forName;
- internal static AndroidRuntime? androidRuntime;
+ internal static JniRuntime? androidRuntime;
[UnmanagedCallersOnly]
static unsafe void RegisterJniNatives (IntPtr typeName_ptr, int typeName_len, IntPtr jniClass, IntPtr methods_ptr, int methods_len)
@@ -78,6 +78,14 @@ static Type TypeGetType (string typeName) =>
((AndroidTypeManager)androidRuntime!.TypeManager).RegisterNativeMembers (jniType, type, methods);
}
+ // NOTE: should have different name than `Initialize` to avoid:
+ // * Assertion at /__w/1/s/src/mono/mono/metadata/icall.c:6258, condition `!only_unmanaged_callers_only' not met
+ internal static void InitializeJniRuntime (JniRuntime runtime)
+ {
+ androidRuntime = runtime;
+ ValueManager = runtime.ValueManager;
+ }
+
[UnmanagedCallersOnly]
internal static unsafe void Initialize (JnienvInitializeArgs* args)
{
diff --git a/src/Mono.Android/Properties/AssemblyInfo.cs.in b/src/Mono.Android/Properties/AssemblyInfo.cs.in
index 14f600a3113..9d8b6f5694b 100644
--- a/src/Mono.Android/Properties/AssemblyInfo.cs.in
+++ b/src/Mono.Android/Properties/AssemblyInfo.cs.in
@@ -45,5 +45,7 @@ using System.Runtime.Versioning;
[assembly: InternalsVisibleTo("Mono.Android-TestsMultiDex, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")]
[assembly: InternalsVisibleTo("Mono.Android-TestsAppBundle, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")]
[assembly: InternalsVisibleTo("Mono.Android.NET-Tests, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")]
+// Temporary for samples/NativeAOT
+[assembly: InternalsVisibleTo("NativeAOT, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")]
[assembly: SuppressMessage ("ApiDesign", "RS0016:Add public types and members to the declared API", Justification = "Analyzer fails due to extended characters.", Scope = "member", Target = "~F:Android.Util.Patterns.GoodIriChar")]
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets
index 868a8fa9f19..de6970db4d0 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets
@@ -30,8 +30,10 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
BeforeTargets="SetupProperties">
- <_NdkSysrootAbi>aarch64-linux-android
- <_NdkClangPrefix>aarch64-linux-android21-
+ <_NdkAbi Condition=" '$(RuntimeIdentifier)' == 'android-arm64' ">aarch64
+ <_NdkAbi Condition=" '$(RuntimeIdentifier)' == 'android-x64' ">x86_64
+ <_NdkSysrootAbi>$(_NdkAbi)-linux-android
+ <_NdkClangPrefix>$(_NdkAbi)-linux-android21-
<_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('windows')) ">windows-x86_64
<_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('osx')) ">darwin-x86_64
<_NdkPrebuiltAbi Condition=" $([MSBuild]::IsOSPlatform('linux')) ">linux-x86_64
@@ -49,11 +51,14 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
-->
<_OriginalSuppressTrimAnalysisWarnings>$(SuppressTrimAnalysisWarnings)
true
+
+ <_ExtraTrimmerArgs>$(_ExtraTrimmerArgs) --notrimwarn
<_OriginalRuntimeIdentifier>$(RuntimeIdentifier)
linux-bionic-arm64
+ linux-bionic-x64
<_targetOS>linux
@@ -78,6 +83,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
+
+
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index 24badd0933b..33143c74a8d 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -5,6 +5,7 @@
using System.Text;
using System.Xml.Linq;
using System.Xml.XPath;
+using Microsoft.VisualStudio.TestPlatform.Utilities;
using Mono.Cecil;
using NUnit.Framework;
@@ -1277,5 +1278,30 @@ public static void logEvent(String eventName) {{
RunProjectAndAssert (proj, builder);
}
+ [Test]
+ public void NativeAOTSample ()
+ {
+ string [] properties = [
+ $"AndroidNdkDirectory={AndroidNdkPath}",
+ "Configuration=Release",
+ ];
+ var projectDirectory = Path.Combine (XABuildPaths.TopDirectory, "samples", "NativeAOT");
+ try {
+ var dotnet = new DotNetCLI (Path.Combine (projectDirectory, "NativeAOT.csproj"));
+ Assert.IsTrue (dotnet.Build (target: "Run", parameters: properties), "`dotnet build -t:Run` should succeed");
+
+ bool didLaunch = WaitForActivityToStart ("my", "MainActivity",
+ Path.Combine (projectDirectory, "logcat.log"), 30);
+ Assert.IsTrue (didLaunch, "Activity should have started.");
+ } catch {
+ foreach (var file in Directory.GetFiles (projectDirectory, "*.log", SearchOption.AllDirectories)) {
+ TestContext.AddTestAttachment (file);
+ }
+ foreach (var bl in Directory.GetFiles (projectDirectory, "*.binlog", SearchOption.AllDirectories)) {
+ TestContext.AddTestAttachment (bl);
+ }
+ throw;
+ }
+ }
}
}