Skip to content

Add EmulatorRunner for emulator CLI operations#284

Open
rmarinho wants to merge 6 commits intomainfrom
feature/emulator-runner
Open

Add EmulatorRunner for emulator CLI operations#284
rmarinho wants to merge 6 commits intomainfrom
feature/emulator-runner

Conversation

@rmarinho
Copy link
Member

@rmarinho rmarinho commented Feb 23, 2026

Summary

Wraps emulator CLI operations for starting and managing Android emulators. Addresses #278.

Changes

New constructor overload with JDK path

public EmulatorRunner (Func<string?> getSdkPath, Func<string?>? getJdkPath)

Sets JAVA_HOME and ANDROID_HOME on all child process ProcessStartInfo via ConfigureEnvironment().
The existing 1-arg constructor remains backward compatible.

Updated: StartAvd / ListAvdNamesAsync

Now call ConfigureEnvironment(psi) before starting processes.

API

public class EmulatorRunner
{
    public EmulatorRunner (Func<string?> getSdkPath);
    public EmulatorRunner (Func<string?> getSdkPath, Func<string?>? getJdkPath);
    
    public string? EmulatorPath { get; }
    public bool IsAvailable { get; }
    
    Process StartAvd (string avdName, bool coldBoot = false, string? additionalArgs = null);
    Task<List<string>> ListAvdNamesAsync (CancellationToken ct = default);
}

Dependencies

Requires #283 (AdbRunner) for the adb-runner branch.

Copilot AI review requested due to automatic review settings February 23, 2026 17:39
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new EmulatorRunner to Xamarin.Android.Tools.AndroidSdk intended to wrap Android emulator CLI operations, alongside new shared infrastructure for running Android SDK command-line tools with environment setup and result modeling.

Changes:

  • Added EmulatorRunner to start an AVD, stop an emulator, and list available AVD names.
  • Added AndroidToolRunner utility to run SDK tools sync/async (with timeouts) and to start long-running background processes.
  • Added AndroidEnvironmentHelper and ToolRunnerResult / ToolRunnerResult<T> to standardize tool environment and execution results.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs Introduces emulator wrapper methods (start/stop/list AVDs) built on the tool runner infrastructure.
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidToolRunner.cs Adds process execution helpers (sync/async + background) with timeout/output capture.
src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs Adds env var setup and mapping helpers (ABI/API/tag display names).
src/Xamarin.Android.Tools.AndroidSdk/Models/ToolRunnerResult.cs Adds a shared result model for tool execution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@rmarinho rmarinho added the copilot `copilot-cli` or other AIs were used to author this label Feb 23, 2026
@rmarinho rmarinho requested a review from Redth February 23, 2026 17:51
@rmarinho rmarinho requested a review from mattleibow February 23, 2026 17:51
@jonathanpeppers
Copy link
Member

I'd like to get the System.Diagnostics.Process code unified like mentioned here:

rmarinho added a commit that referenced this pull request Feb 24, 2026
Addresses PR #284 feedback to use existing ProcessUtils instead of
the removed AndroidToolRunner. Simplifies API:

- Methods now throw InvalidOperationException on failure
- Uses ProcessUtils.RunToolAsync() and StartToolBackground()
- Removed complex ToolRunnerResult wrapper types

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/emulator-runner branch from f1aa44f to 826d4aa Compare February 24, 2026 14:15
rmarinho added a commit that referenced this pull request Feb 24, 2026
Addresses PR #283/#284 feedback to use existing ProcessUtils.
Simplifies API by throwing exceptions on failure instead of
returning result types with error states.

Changes:
- AdbRunner: Simplified using ProcessUtils.RunToolAsync()
- EmulatorRunner: Uses ProcessUtils.StartToolBackground()
- Removed duplicate AndroidDeviceInfo from Models directory

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/emulator-runner branch 2 times, most recently from 39617c8 to 5268300 Compare February 24, 2026 19:09
@rmarinho rmarinho requested a review from Copilot February 24, 2026 19:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 4 comments.

Comment on lines +14 to +18
/// Runs Android Emulator commands.
/// </summary>
public class EmulatorRunner
{
readonly Func<string?> getSdkPath;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description/New API lists StartEmulator/StartEmulatorAsync, WaitForBootAsync, and ListRunningEmulatorsAsync, but this class currently only implements StartAvd and ListAvdNamesAsync (and the signature differs). Either update the implementation to match the proposed API surface or adjust the PR description/issue link expectations so consumers aren’t broken/misled.

Copilot uses AI. Check for mistakes.
Comment on lines +64 to +68
var args = $"-avd \"{avdName}\"";
if (coldBoot)
args += " -no-snapshot-load";
if (!string.IsNullOrEmpty (additionalArgs))
args += " " + additionalArgs;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StartAvd builds a single argument string by interpolating avdName and appending additionalArgs verbatim. If avdName contains quotes/whitespace (or additionalArgs comes from untrusted input), this can break parsing or allow argument injection. Consider validating/escaping avdName (e.g., reject quotes) and taking additional args as a structured list (e.g., IEnumerable) with proper quoting/escaping when constructing Arguments.

Copilot uses AI. Check for mistakes.
if (!IsAvailable)
throw new InvalidOperationException ("Android Emulator not found.");

var stdout = new StringWriter ();
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ListAvdNamesAsync allocates a StringWriter for stdout but never disposes it. Use a using/using var to dispose the writer (and to keep the pattern consistent with other code that disposes disposable resources).

Suggested change
var stdout = new StringWriter ();
using var stdout = new StringWriter ();

Copilot uses AI. Check for mistakes.
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A list of AVD names.</returns>
/// <exception cref="InvalidOperationException">Thrown when Android Emulator is not found.</exception>
public async Task<List<string>> ListAvdNamesAsync (CancellationToken cancellationToken = default)
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Public API returns Task<List>, which exposes a mutable concrete collection. Consider returning Task<IReadOnlyList> (or IReadOnlyList) to avoid leaking mutability and to align with the API shape described in the PR metadata.

Suggested change
public async Task<List<string>> ListAvdNamesAsync (CancellationToken cancellationToken = default)
public async Task<IReadOnlyList<string>> ListAvdNamesAsync (CancellationToken cancellationToken = default)

Copilot uses AI. Check for mistakes.
rmarinho and others added 5 commits February 26, 2026 17:31
- StartAvd(): Start an emulator for an AVD
- ListAvdNamesAsync(): List installed AVD names

Minimal implementation without external dependencies.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add XML documentation to all public members
- Use 'is not null' instead of '!= null'
- Improve code formatting
- Remove unused variable assignment

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add optional Func<string?> getJdkPath constructor parameter
- ConfigureEnvironment() sets JAVA_HOME and ANDROID_HOME on all ProcessStartInfo
- Applied to StartAvd and ListAvdNamesAsync
- Backward compatible: existing 1-arg constructor still works

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/emulator-runner branch from 4957925 to 9b53526 Compare February 26, 2026 17:31
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants