diff --git a/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs
new file mode 100644
index 00000000..d745a717
--- /dev/null
+++ b/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs
@@ -0,0 +1,110 @@
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Xamarin.Android.Tools
+{
+ ///
+ /// Runs Android Emulator commands.
+ ///
+ public class EmulatorRunner
+ {
+ readonly Func getSdkPath;
+ readonly Func? getJdkPath;
+
+ public EmulatorRunner (Func getSdkPath)
+ : this (getSdkPath, null)
+ {
+ }
+
+ public EmulatorRunner (Func getSdkPath, Func? getJdkPath)
+ {
+ this.getSdkPath = getSdkPath ?? throw new ArgumentNullException (nameof (getSdkPath));
+ this.getJdkPath = getJdkPath;
+ }
+
+ public string? EmulatorPath {
+ get {
+ var sdkPath = getSdkPath ();
+ if (string.IsNullOrEmpty (sdkPath))
+ return null;
+
+ var ext = OS.IsWindows ? ".exe" : "";
+ var path = Path.Combine (sdkPath, "emulator", "emulator" + ext);
+
+ return File.Exists (path) ? path : null;
+ }
+ }
+
+ public bool IsAvailable => EmulatorPath is not null;
+
+ void ConfigureEnvironment (ProcessStartInfo psi)
+ {
+ var sdkPath = getSdkPath ();
+ if (!string.IsNullOrEmpty (sdkPath))
+ psi.EnvironmentVariables ["ANDROID_HOME"] = sdkPath;
+
+ var jdkPath = getJdkPath?.Invoke ();
+ if (!string.IsNullOrEmpty (jdkPath))
+ psi.EnvironmentVariables ["JAVA_HOME"] = jdkPath;
+ }
+
+ public Process StartAvd (string avdName, bool coldBoot = false, string? additionalArgs = null)
+ {
+ if (!IsAvailable)
+ throw new InvalidOperationException ("Android Emulator not found.");
+
+ var args = $"-avd \"{avdName}\"";
+ if (coldBoot)
+ args += " -no-snapshot-load";
+ if (!string.IsNullOrEmpty (additionalArgs))
+ args += " " + additionalArgs;
+
+ var psi = new ProcessStartInfo {
+ FileName = EmulatorPath!,
+ Arguments = args,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ ConfigureEnvironment (psi);
+
+ var process = new Process { StartInfo = psi };
+ process.Start ();
+
+ return process;
+ }
+
+ public async Task> ListAvdNamesAsync (CancellationToken cancellationToken = default)
+ {
+ if (!IsAvailable)
+ throw new InvalidOperationException ("Android Emulator not found.");
+
+ var stdout = new StringWriter ();
+ var psi = new ProcessStartInfo {
+ FileName = EmulatorPath!,
+ Arguments = "-list-avds",
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+ ConfigureEnvironment (psi);
+
+ await ProcessUtils.StartProcess (psi, stdout, null, cancellationToken).ConfigureAwait (false);
+
+ var avds = new List ();
+ foreach (var line in stdout.ToString ().Split ('\n')) {
+ var trimmed = line.Trim ();
+ if (!string.IsNullOrEmpty (trimmed))
+ avds.Add (trimmed);
+ }
+
+ return avds;
+ }
+ }
+}
+