diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets
index b4e7fb22dfa962..d89c3e3703eeb7 100644
--- a/eng/testing/tests.mobile.targets
+++ b/eng/testing/tests.mobile.targets
@@ -25,6 +25,13 @@
true
+
+
+
+
+ <_MobileIntermediateOutputPath>$(IntermediateOutputPath)mobile
+
+
AndroidTestRunner.dll
+
+
+ @(MonoAOTCompilerDefaultAotArguments, ';')
+ @(MonoAOTCompilerDefaultProcessArguments, ';')
+
+
-
+
+
+
+
+
+
+
+ AppDir="$(PublishDir)">
@@ -80,10 +110,6 @@
-
-
-
-
diff --git a/src/mono/sample/Android/AndroidSampleApp.csproj b/src/mono/sample/Android/AndroidSampleApp.csproj
index d096cac023ebac..0853fd97915bb4 100644
--- a/src/mono/sample/Android/AndroidSampleApp.csproj
+++ b/src/mono/sample/Android/AndroidSampleApp.csproj
@@ -8,6 +8,7 @@
true
Link
$(ArtifactsBinDir)microsoft.netcore.app.runtime.$(RuntimeIdentifier)\$(Configuration)\runtimes\android-$(TargetArchitecture)\
+ false
@@ -23,6 +24,10 @@
+
+
+ <_MobileIntermediateOutputPath>$(IntermediateOutputPath)mobile
+
@@ -39,6 +44,8 @@
+
@@ -59,12 +66,13 @@
-
@@ -72,13 +80,15 @@
+ OutputDir="$(ApkDir)"
+ AppDir="$(PublishDir)">
diff --git a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs
index f9c88cb5a1d957..af036e41e27375 100644
--- a/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs
+++ b/src/tasks/AndroidAppBuilder/AndroidAppBuilder.cs
@@ -10,22 +10,36 @@
public class AndroidAppBuilderTask : Task
{
[Required]
- public string SourceDir { get; set; } = ""!;
+ public string MonoRuntimeHeaders { get; set; } = ""!;
+ ///
+ /// Target directory with *dll and other content to be AOT'd and/or bundled
+ ///
[Required]
- public string MonoRuntimeHeaders { get; set; } = ""!;
+ public string AppDir { get; set; } = ""!;
///
/// This library will be used as an entry-point (e.g. TestRunner.dll)
///
public string MainLibraryFileName { get; set; } = ""!;
+ ///
+ /// List of paths to assemblies to be included in the app. For AOT builds the 'ObjectFile' metadata key needs to point to the object file.
+ ///
+ public ITaskItem[] Assemblies { get; set; } = Array.Empty();
+
+ ///
+ /// Prefer FullAOT mode for Emulator over JIT
+ ///
+ public bool ForceAOT { get; set; }
+
[Required]
public string RuntimeIdentifier { get; set; } = ""!;
[Required]
public string OutputDir { get; set; } = ""!;
+ [Required]
public string? ProjectName { get; set; }
public string? AndroidSdk { get; set; }
@@ -64,6 +78,7 @@ public override bool Execute()
var apkBuilder = new ApkBuilder();
apkBuilder.ProjectName = ProjectName;
+ apkBuilder.AppDir = AppDir;
apkBuilder.OutputDir = OutputDir;
apkBuilder.AndroidSdk = AndroidSdk;
apkBuilder.AndroidNdk = AndroidNdk;
@@ -74,7 +89,9 @@ public override bool Execute()
apkBuilder.NativeMainSource = NativeMainSource;
apkBuilder.KeyStorePath = KeyStorePath;
apkBuilder.ForceInterpreter = ForceInterpreter;
- (ApkBundlePath, ApkPackageId) = apkBuilder.BuildApk(SourceDir, abi, MainLibraryFileName, MonoRuntimeHeaders);
+ apkBuilder.ForceAOT = ForceAOT;
+ apkBuilder.Assemblies = Assemblies;
+ (ApkBundlePath, ApkPackageId) = apkBuilder.BuildApk(abi, MainLibraryFileName, MonoRuntimeHeaders);
return true;
}
diff --git a/src/tasks/AndroidAppBuilder/ApkBuilder.cs b/src/tasks/AndroidAppBuilder/ApkBuilder.cs
index 71a83cf2a65b59..0ce54fd15c1d6d 100644
--- a/src/tasks/AndroidAppBuilder/ApkBuilder.cs
+++ b/src/tasks/AndroidAppBuilder/ApkBuilder.cs
@@ -5,12 +5,14 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using Microsoft.Build.Framework;
public class ApkBuilder
{
private const string DefaultMinApiLevel = "21";
public string? ProjectName { get; set; }
+ public string? AppDir { get; set; }
public string? AndroidNdk { get; set; }
public string? AndroidSdk { get; set; }
public string? MinApiLevel { get; set; }
@@ -21,49 +23,57 @@ public class ApkBuilder
public string? NativeMainSource { get; set; }
public string? KeyStorePath { get; set; }
public bool ForceInterpreter { get; set; }
+ public bool ForceAOT { get; set; }
+ public ITaskItem[] Assemblies { get; set; } = Array.Empty();
public (string apk, string packageId) BuildApk(
- string sourceDir,
string abi,
- string entryPointLib,
+ string mainLibraryFileName,
string monoRuntimeHeaders)
{
- if (!Directory.Exists(sourceDir))
- throw new ArgumentException($"sourceDir='{sourceDir}' is empty or doesn't exist");
-
- if (string.IsNullOrEmpty(abi))
- throw new ArgumentException("abi shoudln't be empty (e.g. x86, x86_64, armeabi-v7a or arm64-v8a");
+ if (string.IsNullOrEmpty(AppDir) || !Directory.Exists(AppDir))
+ {
+ throw new ArgumentException($"AppDir='{AppDir}' is empty or doesn't exist");
+ }
- string entryPointLibPath = "";
- if (!string.IsNullOrEmpty(entryPointLib))
+ if (!string.IsNullOrEmpty(mainLibraryFileName) && !File.Exists(Path.Combine(AppDir, mainLibraryFileName)))
{
- entryPointLibPath = Path.Combine(sourceDir, entryPointLib);
- if (!File.Exists(entryPointLibPath))
- throw new ArgumentException($"{entryPointLib} was not found in sourceDir='{sourceDir}'");
+ throw new ArgumentException($"MainLibraryFileName='{mainLibraryFileName}' was not found in AppDir='{AppDir}'");
}
- if (string.IsNullOrEmpty(ProjectName))
+ if (string.IsNullOrEmpty(abi))
{
- if (string.IsNullOrEmpty(entryPointLib))
- throw new ArgumentException("ProjectName needs to be set if entryPointLib is empty.");
- else
- ProjectName = Path.GetFileNameWithoutExtension(entryPointLib);
+ throw new ArgumentException("abi should not be empty (e.g. x86, x86_64, armeabi-v7a or arm64-v8a");
}
- if (ProjectName.Contains(' '))
- throw new ArgumentException($"ProjectName='{ProjectName}' shouldn't not contain spaces.");
+ if (!string.IsNullOrEmpty(ProjectName) && ProjectName.Contains(' '))
+ {
+ throw new ArgumentException($"ProjectName='{ProjectName}' should not not contain spaces.");
+ }
- if (string.IsNullOrEmpty(AndroidSdk))
+ if (string.IsNullOrEmpty(AndroidSdk)){
AndroidSdk = Environment.GetEnvironmentVariable("ANDROID_SDK_ROOT");
+ }
if (string.IsNullOrEmpty(AndroidNdk))
+ {
AndroidNdk = Environment.GetEnvironmentVariable("ANDROID_NDK_ROOT");
+ }
if (string.IsNullOrEmpty(AndroidSdk) || !Directory.Exists(AndroidSdk))
+ {
throw new ArgumentException($"Android SDK='{AndroidSdk}' was not found or empty (can be set via ANDROID_SDK_ROOT envvar).");
+ }
if (string.IsNullOrEmpty(AndroidNdk) || !Directory.Exists(AndroidNdk))
+ {
throw new ArgumentException($"Android NDK='{AndroidNdk}' was not found or empty (can be set via ANDROID_NDK_ROOT envvar).");
+ }
+
+ if (ForceInterpreter && ForceAOT)
+ {
+ throw new InvalidOperationException("Interpreter and AOT cannot be enabled at the same time");
+ }
// Try to get the latest build-tools version if not specified
if (string.IsNullOrEmpty(BuildToolsVersion))
@@ -88,7 +98,25 @@ public class ApkBuilder
string buildToolsFolder = Path.Combine(AndroidSdk, "build-tools", BuildToolsVersion);
if (!Directory.Exists(buildToolsFolder))
+ {
throw new ArgumentException($"{buildToolsFolder} was not found.");
+ }
+
+ var assemblerFiles = new List();
+ foreach (ITaskItem file in Assemblies)
+ {
+ // use AOT files if available
+ var obj = file.GetMetadata("AssemblerFile");
+ if (!string.IsNullOrEmpty(obj))
+ {
+ assemblerFiles.Add(obj);
+ }
+ }
+
+ if (ForceAOT && !assemblerFiles.Any())
+ {
+ throw new InvalidOperationException("Need list of AOT files.");
+ }
Directory.CreateDirectory(OutputDir);
Directory.CreateDirectory(Path.Combine(OutputDir, "bin"));
@@ -105,7 +133,7 @@ public class ApkBuilder
// Copy sourceDir to OutputDir/assets-tozip (ignore native files)
// these files then will be zipped and copied to apk/assets/assets.zip
- Utils.DirectoryCopy(sourceDir, Path.Combine(OutputDir, "assets-tozip"), file =>
+ Utils.DirectoryCopy(AppDir, Path.Combine(OutputDir, "assets-tozip"), file =>
{
string fileName = Path.GetFileName(file);
string extension = Path.GetExtension(file);
@@ -143,13 +171,41 @@ public class ApkBuilder
// 1. Build libmonodroid.so` via cmake
- string monoRuntimeLib = Path.Combine(sourceDir, "libmonosgen-2.0.a");
+ string monoRuntimeLib = Path.Combine(AppDir, "libmonosgen-2.0.a");
if (!File.Exists(monoRuntimeLib))
- throw new ArgumentException($"libmonosgen-2.0.a was not found in {sourceDir}");
+ {
+ throw new ArgumentException($"libmonosgen-2.0.a was not found in {AppDir}");
+ }
+ else
+ {
+ monoRuntimeLib = $" {monoRuntimeLib}{Environment.NewLine}";
+ }
+
+ string aotSources = "";
+ foreach (string asm in assemblerFiles)
+ {
+ // these libraries are linked via modules.c
+ aotSources += $" {asm}{Environment.NewLine}";
+ }
string cmakeLists = Utils.GetEmbeddedResource("CMakeLists-android.txt")
.Replace("%MonoInclude%", monoRuntimeHeaders)
- .Replace("%NativeLibrariesToLink%", monoRuntimeLib);
+ .Replace("%NativeLibrariesToLink%", monoRuntimeLib)
+ .Replace("%AotSources%", aotSources)
+ .Replace("%AotModulesSource%", string.IsNullOrEmpty(aotSources) ? "" : "modules.c");
+
+ string defines = "";
+ if (ForceInterpreter)
+ {
+ defines = "add_definitions(-DFORCE_INTERPRETER=1)";
+ }
+ else if (ForceAOT)
+ {
+ defines = "add_definitions(-DFORCE_AOT=1)";
+ }
+
+ cmakeLists = cmakeLists.Replace("%Defines%", defines);
+
File.WriteAllText(Path.Combine(OutputDir, "CMakeLists.txt"), cmakeLists);
File.WriteAllText(Path.Combine(OutputDir, "monodroid.c"), Utils.GetEmbeddedResource("monodroid.c"));
@@ -186,13 +242,13 @@ public class ApkBuilder
File.WriteAllText(javaActivityPath,
Utils.GetEmbeddedResource("MainActivity.java")
- .Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib)));
+ .Replace("%EntryPointLibName%", Path.GetFileName(mainLibraryFileName)));
if (!string.IsNullOrEmpty(NativeMainSource))
File.Copy(NativeMainSource, javaActivityPath, true);
string monoRunner = Utils.GetEmbeddedResource("MonoRunner.java")
- .Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib))
- .Replace("%ForceInterpreter%", ForceInterpreter.ToString().ToLower());
+ .Replace("%EntryPointLibName%", Path.GetFileName(mainLibraryFileName));
+
File.WriteAllText(monoRunnerPath, monoRunner);
File.WriteAllText(Path.Combine(OutputDir, "AndroidManifest.xml"),
@@ -213,7 +269,7 @@ public class ApkBuilder
var dynamicLibs = new List();
dynamicLibs.Add(Path.Combine(OutputDir, "monodroid", "libmonodroid.so"));
- dynamicLibs.AddRange(Directory.GetFiles(sourceDir, "*.so").Where(file => Path.GetFileName(file) != "libmonodroid.so"));
+ dynamicLibs.AddRange(Directory.GetFiles(AppDir, "*.so").Where(file => Path.GetFileName(file) != "libmonodroid.so"));
// add all *.so files to lib/%abi%/
Directory.CreateDirectory(Path.Combine(OutputDir, "lib", abi));
diff --git a/src/tasks/AndroidAppBuilder/Templates/CMakeLists-android.txt b/src/tasks/AndroidAppBuilder/Templates/CMakeLists-android.txt
index 48e1d080fcd5d3..c74d4625af0fa0 100644
--- a/src/tasks/AndroidAppBuilder/Templates/CMakeLists-android.txt
+++ b/src/tasks/AndroidAppBuilder/Templates/CMakeLists-android.txt
@@ -2,10 +2,21 @@ cmake_minimum_required(VERSION 3.10)
project(monodroid)
+enable_language(C ASM)
+
+if(NOT USE_LLVM)
+ # the assembler code we generate is GNU which isn't understood by llvm
+ add_compile_options(-no-integrated-as)
+endif()
+
add_library(
monodroid
SHARED
- monodroid.c)
+ monodroid.c
+ %AotModulesSource%
+ %AotSources%)
+
+%Defines%
include_directories("%MonoInclude%")
diff --git a/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java b/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java
index 5618fdd61504c9..ad5fa5348a4144 100644
--- a/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java
+++ b/src/tasks/AndroidAppBuilder/Templates/MonoRunner.java
@@ -43,7 +43,6 @@ public class MonoRunner extends Instrumentation
static String entryPointLibName = "%EntryPointLibName%";
static Bundle result = new Bundle();
- static boolean forceInterpreter = %ForceInterpreter%;
@Override
public void onCreate(Bundle arguments) {
@@ -84,7 +83,7 @@ public static int initialize(String entryPointLibName, Context context) {
unzipAssets(context, filesDir, "assets.zip");
Log.i("DOTNET", "MonoRunner initialize,, entryPointLibName=" + entryPointLibName);
- return initRuntime(filesDir, cacheDir, docsDir, entryPointLibName, forceInterpreter);
+ return initRuntime(filesDir, cacheDir, docsDir, entryPointLibName);
}
@Override
@@ -145,7 +144,7 @@ static void unzipAssets(Context context, String toPath, String zipName) {
}
}
- static native int initRuntime(String libsDir, String cacheDir, String docsDir, String entryPointLibName, boolean forceInterpreter);
+ static native int initRuntime(String libsDir, String cacheDir, String docsDir, String entryPointLibName);
static native int setEnv(String key, String value);
}
diff --git a/src/tasks/AndroidAppBuilder/Templates/monodroid.c b/src/tasks/AndroidAppBuilder/Templates/monodroid.c
index 8d4ae5a5ac8d8b..56cd77a65e3947 100644
--- a/src/tasks/AndroidAppBuilder/Templates/monodroid.c
+++ b/src/tasks/AndroidAppBuilder/Templates/monodroid.c
@@ -13,16 +13,18 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
#include
+#include
#include
#include
static char *bundle_path;
static char *executable;
-static bool force_interpreter;
#define LOG_INFO(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "DOTNET", fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, "DOTNET", fmt, ##__VA_ARGS__)
@@ -80,6 +82,46 @@ mono_droid_assembly_preload_hook (MonoAssemblyName *aname, char **assemblies_pat
return mono_droid_load_assembly (name, culture);
}
+static unsigned char *
+load_aot_data (MonoAssembly *assembly, int size, void *user_data, void **out_handle)
+{
+ *out_handle = NULL;
+
+ char path [1024];
+ int res;
+
+ MonoAssemblyName *assembly_name = mono_assembly_get_name (assembly);
+ const char *aname = mono_assembly_name_get_name (assembly_name);
+
+ LOG_INFO ("Looking for aot data for assembly '%s'.", aname);
+ res = snprintf (path, sizeof (path) - 1, "%s/%s.aotdata", bundle_path, aname);
+ assert (res > 0);
+
+ int fd = open (path, O_RDONLY);
+ if (fd < 0) {
+ LOG_INFO ("Could not load the aot data for %s from %s: %s\n", aname, path, strerror (errno));
+ return NULL;
+ }
+
+ void *ptr = mmap (NULL, size, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0);
+ if (ptr == MAP_FAILED) {
+ LOG_INFO ("Could not map the aot file for %s: %s\n", aname, strerror (errno));
+ close (fd);
+ return NULL;
+ }
+
+ close (fd);
+ LOG_INFO ("Loaded aot data for %s.\n", aname);
+ *out_handle = ptr;
+ return (unsigned char *) ptr;
+}
+
+static void
+free_aot_data (MonoAssembly *assembly, int size, void *user_data, void *handle)
+{
+ munmap (handle, size);
+}
+
char *
strdup_printf (const char *msg, ...)
{
@@ -147,6 +189,10 @@ log_callback (const char *log_domain, const char *log_level, const char *message
}
}
+#if FORCE_AOT
+void register_aot_modules (void);
+#endif
+
int
mono_droid_runtime_init (void)
{
@@ -174,6 +220,7 @@ mono_droid_runtime_init (void)
mono_debug_init (MONO_DEBUG_FORMAT_MONO);
mono_install_assembly_preload_hook (mono_droid_assembly_preload_hook, NULL);
+ mono_install_load_aot_data_hook (load_aot_data, free_aot_data, NULL);
mono_install_unhandled_exception_hook (unhandled_exception_handler, NULL);
mono_trace_set_log_handler (log_callback, NULL);
mono_set_signal_chaining (true);
@@ -184,10 +231,13 @@ mono_droid_runtime_init (void)
mono_jit_parse_options (1, options);
}
- if (force_interpreter) {
- LOG_INFO("Interp Enabled");
- mono_jit_set_aot_mode(MONO_AOT_MODE_INTERP_ONLY);
- }
+#if FORCE_INTERPRETER
+ LOG_INFO("Interp Enabled");
+ mono_jit_set_aot_mode(MONO_AOT_MODE_INTERP_ONLY);
+#elif FORCE_AOT
+ register_aot_modules();
+ mono_jit_set_aot_mode(MONO_AOT_MODE_FULL);
+#endif
mono_jit_init_version ("dotnet.android", "mobile");
@@ -224,7 +274,7 @@ Java_net_dot_MonoRunner_setEnv (JNIEnv* env, jobject thiz, jstring j_key, jstrin
}
int
-Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir, jstring j_docs_dir, jstring j_entryPointLibName, jboolean j_forceInterpreter)
+Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_dir, jstring j_cache_dir, jstring j_docs_dir, jstring j_entryPointLibName)
{
char file_dir[2048];
char cache_dir[2048];
@@ -237,7 +287,6 @@ Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_
bundle_path = file_dir;
executable = entryPointLibName;
- force_interpreter = (bool)j_forceInterpreter;
setenv ("HOME", bundle_path, true);
setenv ("TMPDIR", cache_dir, true);
@@ -245,3 +294,11 @@ Java_net_dot_MonoRunner_initRuntime (JNIEnv* env, jobject thiz, jstring j_files_
return mono_droid_runtime_init ();
}
+
+// called from C#
+void
+invoke_external_native_api (void (*callback)(void))
+{
+ if (callback)
+ callback();
+}
diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs
index 7e5baee6f81dd7..d4cd019dce52e9 100644
--- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs
+++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs
@@ -390,11 +390,13 @@ private void GenerateAotModulesTable(ITaskItem[] assemblies, string[]? profilers
_fileWrites.Add(AotModulesTablePath!);
if (parsedAotModulesTableLanguage == MonoAotModulesTableLanguage.C)
{
+ writer.WriteLine("#include ");
+
foreach (var symbol in symbols)
{
writer.WriteLine($"extern void *{symbol};");
}
- writer.WriteLine("static void register_aot_modules ()");
+ writer.WriteLine("void register_aot_modules ()");
writer.WriteLine("{");
foreach (var symbol in symbols)
{
diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.props b/src/tasks/AotCompilerTask/MonoAOTCompiler.props
index db518b2e8455da..852a5330a0e733 100644
--- a/src/tasks/AotCompilerTask/MonoAOTCompiler.props
+++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.props
@@ -14,6 +14,9 @@
+
+
+
diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/AOT/Android.Device_Emulator.Aot.Test.csproj b/src/tests/FunctionalTests/Android/Device_Emulator/AOT/Android.Device_Emulator.Aot.Test.csproj
new file mode 100644
index 00000000000000..49fd6653dde851
--- /dev/null
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/AOT/Android.Device_Emulator.Aot.Test.csproj
@@ -0,0 +1,16 @@
+
+
+ Exe
+ false
+ true
+ true
+ $(NetCoreAppCurrent)
+ Android.Device_Emulator.Aot.Test.dll
+ 42
+ true
+
+
+
+
+
+
diff --git a/src/tests/FunctionalTests/Android/Emulator/Interpreter/Program.cs b/src/tests/FunctionalTests/Android/Device_Emulator/AOT/Program.cs
similarity index 100%
rename from src/tests/FunctionalTests/Android/Emulator/Interpreter/Program.cs
rename to src/tests/FunctionalTests/Android/Device_Emulator/AOT/Program.cs
diff --git a/src/tests/FunctionalTests/Android/Emulator/Interpreter/Android.Emulator.Interpreter.Test.csproj b/src/tests/FunctionalTests/Android/Device_Emulator/Interpreter/Android.Device_Emulator.Interpreter.Test.csproj
similarity index 82%
rename from src/tests/FunctionalTests/Android/Emulator/Interpreter/Android.Emulator.Interpreter.Test.csproj
rename to src/tests/FunctionalTests/Android/Device_Emulator/Interpreter/Android.Device_Emulator.Interpreter.Test.csproj
index f20f4348a2637f..cd209e1ddd3a53 100644
--- a/src/tests/FunctionalTests/Android/Emulator/Interpreter/Android.Emulator.Interpreter.Test.csproj
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/Interpreter/Android.Device_Emulator.Interpreter.Test.csproj
@@ -5,7 +5,7 @@
false
true
$(NetCoreAppCurrent)
- Android.Emulator.Interpreter.Test.dll
+ Android.Device_Emulator.Interpreter.Test.dll
42
diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/Interpreter/Program.cs b/src/tests/FunctionalTests/Android/Device_Emulator/Interpreter/Program.cs
new file mode 100644
index 00000000000000..7dcc0f375db878
--- /dev/null
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/Interpreter/Program.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+public static class Program
+{
+ public static int Main(string[] args)
+ {
+ Console.WriteLine("Hello, Android!"); // logcat
+ return 42;
+ }
+}
diff --git a/src/tests/FunctionalTests/Android/Emulator/JIT/Android.Emulator.JIT.Test.csproj b/src/tests/FunctionalTests/Android/Device_Emulator/JIT/Android.Device_Emulator.JIT.Test.csproj
similarity index 81%
rename from src/tests/FunctionalTests/Android/Emulator/JIT/Android.Emulator.JIT.Test.csproj
rename to src/tests/FunctionalTests/Android/Device_Emulator/JIT/Android.Device_Emulator.JIT.Test.csproj
index 109a6cc549ca7b..3503f290ce73ea 100644
--- a/src/tests/FunctionalTests/Android/Emulator/JIT/Android.Emulator.JIT.Test.csproj
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/JIT/Android.Device_Emulator.JIT.Test.csproj
@@ -4,7 +4,7 @@
false
true
$(NetCoreAppCurrent)
- Android.Emulator.JIT.Test.dll
+ Android.Device_Emulator.JIT.Test.dll
42
diff --git a/src/tests/FunctionalTests/Android/Emulator/JIT/Program.cs b/src/tests/FunctionalTests/Android/Device_Emulator/JIT/Program.cs
similarity index 100%
rename from src/tests/FunctionalTests/Android/Emulator/JIT/Program.cs
rename to src/tests/FunctionalTests/Android/Device_Emulator/JIT/Program.cs
diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/PInvoke/Android.Device_Emulator.PInvoke.Test.csproj b/src/tests/FunctionalTests/Android/Device_Emulator/PInvoke/Android.Device_Emulator.PInvoke.Test.csproj
new file mode 100644
index 00000000000000..3e8c96b95a705c
--- /dev/null
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/PInvoke/Android.Device_Emulator.PInvoke.Test.csproj
@@ -0,0 +1,16 @@
+
+
+ Exe
+ true
+ false
+ true
+ $(NetCoreAppCurrent)
+ Android.Device_Emulator.PInvoke.Test.dll
+ false
+ 42
+
+
+
+
+
+
diff --git a/src/tests/FunctionalTests/Android/Device_Emulator/PInvoke/Program.cs b/src/tests/FunctionalTests/Android/Device_Emulator/PInvoke/Program.cs
new file mode 100644
index 00000000000000..750e83dc4785ad
--- /dev/null
+++ b/src/tests/FunctionalTests/Android/Device_Emulator/PInvoke/Program.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+
+public static class Program
+{
+ [DllImport("__Internal")]
+ unsafe private static extern void invoke_external_native_api(delegate* unmanaged callback);
+
+ private static int counter = 1;
+
+ [UnmanagedCallersOnly]
+ private static void Callback()
+ {
+ counter = 42;
+ }
+
+ public static int Main(string[] args)
+ {
+ unsafe {
+ delegate* unmanaged unmanagedPtr = &Callback;
+ invoke_external_native_api(unmanagedPtr);
+ }
+
+ return counter;
+ }
+}
diff --git a/src/tests/FunctionalTests/Android/Emulator/AOT/README.md b/src/tests/FunctionalTests/Android/Emulator/AOT/README.md
deleted file mode 100644
index 55f50108e401f3..00000000000000
--- a/src/tests/FunctionalTests/Android/Emulator/AOT/README.md
+++ /dev/null
@@ -1 +0,0 @@
-TO-DO: add the test case for AOT mode when https://github.com/dotnet/runtime/pull/43535 has been completed.
\ No newline at end of file
diff --git a/src/tests/run.proj b/src/tests/run.proj
index 73450c6344bbe8..83d512e561e769 100644
--- a/src/tests/run.proj
+++ b/src/tests/run.proj
@@ -473,7 +473,7 @@ namespace $([System.String]::Copy($(Category)).Replace(".","_").Replace("\","").
ProjectName="$(Category)"
MonoRuntimeHeaders="$(MicrosoftNetCoreAppRuntimePackDir)/native/include/mono-2.0"
StripDebugSymbols="$(StripDebugSymbols)"
- SourceDir="$(BuildDir)"
+ AppDir="$(BuildDir)"
OutputDir="$(AppDir)">