Skip to content

Add EmulatorRunner for emulator CLI operations#284

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

Add EmulatorRunner for emulator CLI operations#284
rmarinho wants to merge 41 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

EmulatorRunner — Emulator boot and management

New: BootAndWaitAsync — Ports the boot emulator logic from dotnet/android's BootAndroidEmulator MSBuild task into the shared library.

Three-phase boot:

  1. Check if device is already online in adb devices → pass through
  2. Check if AVD is already running (but still booting) → wait for full boot
  3. Launch emulator + poll until it appears + wait for full boot

Full boot = sys.boot_completed == "1" AND pm path android returns package: prefix.

New: EmulatorBootResult — Result type with Success, Serial, ErrorMessage
New: EmulatorBootOptions — Options type with BootTimeout, AdditionalArgs, ColdBoot, PollInterval

AdbRunner — Shell command helpers

New: GetShellPropertyAsync — Runs adb -s serial shell getprop property
New: RunShellCommandAsync — Runs adb -s serial shell command

These support the boot wait logic (checking sys.boot_completed and pm path android).

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<IReadOnlyList<string>> ListAvdNamesAsync (CancellationToken ct = default);

    // NEW: Boot and wait for emulator to be fully ready
    Task<EmulatorBootResult> BootAndWaitAsync (
        string deviceOrAvdName,
        AdbRunner adbRunner,
        EmulatorBootOptions? options = null,
        Action<TraceLevel, string>? logger = null,
        CancellationToken cancellationToken = default);
}

public class EmulatorBootResult
{
    public bool Success { get; set; }
    public string? Serial { get; set; }
    public string? ErrorMessage { get; set; }
}

public class EmulatorBootOptions
{
    public TimeSpan BootTimeout { get; set; } = TimeSpan.FromSeconds(300);
    public string? AdditionalArgs { get; set; }
    public bool ColdBoot { get; set; }
    public TimeSpan PollInterval { get; set; } = TimeSpan.FromMilliseconds(500);
}

Downstream Consumer

dotnet/android BootAndroidEmulator.cs will delegate to EmulatorRunner.BootAndWaitAsync() via the submodule — similar to how GetAvailableAndroidDevices delegates to AdbRunner.ParseAdbDevicesOutput().

Draft dotnet/android PR to follow.

Tests

13 unit tests (EmulatorRunnerTests.cs):

  • ParseListAvdsOutput: multiple AVDs, empty, Windows newlines, blank lines
  • EmulatorPath: SDK discovery, missing SDK, null SDK
  • AlreadyOnlineDevice_PassesThrough: device already in adb devices
  • AvdAlreadyRunning_WaitsForFullBoot: AVD running, wait for boot completion
  • BootEmulator_AppearsAfterPolling: AVD not running, appears after polling
  • LaunchFailure_ReturnsError: emulator path missing
  • BootTimeout_BootCompletedNeverReaches1: boot timeout
  • MultipleEmulators_FindsCorrectAvd: finds correct AVD among multiple

Test scenarios ported from dotnet/android BootAndroidEmulatorTests.cs.

Dependencies

Requires #283 (AdbRunner) — uses ListDevicesAsync, GetEmulatorAvdNameAsync, GetShellPropertyAsync, RunShellCommandAsync.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

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.

@rmarinho rmarinho force-pushed the feature/emulator-runner branch 5 times, most recently from 1b10889 to ee31e4b Compare March 3, 2026 14:36
@rmarinho
Copy link
Member Author

rmarinho commented Mar 4, 2026

Review feedback addressed — commit references

Feedback Commit Details
Port BootAndroidEmulator logic from dotnet/android 0088e39 BootAndWaitAsync with 3-phase boot, GetShellPropertyAsync, RunShellCommandAsync, 6 new tests

New files:

  • Models/EmulatorBootResult.cs, Models/EmulatorBootOptions.cs
  • Tests: 6 async boot scenarios ported from BootAndroidEmulatorTests.cs

Modified:

  • Runners/EmulatorRunner.csBootAndWaitAsync, FindRunningAvdSerial, WaitForFullBootAsync
  • Runners/AdbRunner.csGetShellPropertyAsync, RunShellCommandAsync (+ ListDevicesAsync made virtual for testability)

Draft dotnet/android consumer PR to follow.

Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

🤖 AI Review Summary

Found 7 issues: 1 correctness, 2 error handling, 1 API design, 1 code duplication, 1 code organization, 1 naming.

  • Correctness: StartAvd redirects stdout/stderr but never drains the pipes — OS buffer fill will deadlock the emulator process (EmulatorRunner.cs:74)
  • API design: AdditionalArgs is a single string — will be treated as one argument by ProcessUtils.ArgumentList, breaking multi-token args like -gpu swiftshader_indirect (EmulatorBootOptions.cs:14)
  • Error handling: ListDevicesAsync ignores the exit code from ProcessUtils.StartProcess while sibling methods in AvdManagerRunner check it consistently (AdbRunner.cs:72)
  • Code duplication: AvdManagerRunner.AvdManagerPath reimplements the cmdline-tools version scanning that ProcessUtils.FindCmdlineTool (added in this same PR) already provides (AvdManagerRunner.cs:33)
  • Error handling: Bare catch { } swallows all exceptions without capturing them (AdbRunner.cs:107)

👍 Solid three-phase boot logic ported faithfully from dotnet/android. Good use of virtual on AdbRunner methods to enable clean test mocking. Thorough test coverage with 13+ unit tests covering parsing, edge cases, and the full boot flow. Nice extraction of AndroidEnvironmentHelper for shared env var setup.


This review was generated by the android-tools-reviewer skill based on review guidelines established by @jonathanpeppers.

// Redirect stdout/stderr so the emulator process doesn't inherit the
// caller's pipes. Without this, parent processes (e.g. VS Code spawn)
// never see the 'close' event because the emulator holds the pipes open.
psi.RedirectStandardOutput = true;
Copy link
Member

Choose a reason for hiding this comment

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

🤖 Pattern / CorrectnessRedirectStandardOutput and RedirectStandardError are set to true but the output is never drained. When the emulator writes enough output to fill the OS pipe buffer (~4–64 KB depending on platform), the write will block and the emulator process will deadlock.

Since StartAvd is a fire-and-forget launch that returns the Process to the caller, the simplest fix is to call BeginOutputReadLine() and BeginErrorReadLine() immediately after Start() to asynchronously discard the output:

process.Start ();
process.BeginOutputReadLine ();
process.BeginErrorReadLine ();

Alternatively, if you don't need to redirect at all (just prevent pipe inheritance), you could set Redirect* = false and rely on UseShellExecute = false (already set by ProcessUtils.CreateProcessStartInfo) which doesn't inherit the caller's console.

Also: using new Process { StartInfo = psi } + process.Start() directly bypasses ProcessUtils. This is an intentional pattern for long-running background processes, but worth a brief comment explaining why ProcessUtils.StartProcess isn't used here (it awaits completion).

Rule: Don't redirect stdout/stderr without draining (Postmortem #48); Use ProcessUtils (Postmortem #15)

public class EmulatorBootOptions
{
public TimeSpan BootTimeout { get; set; } = TimeSpan.FromSeconds (300);
public string? AdditionalArgs { get; set; }
Copy link
Member

Choose a reason for hiding this comment

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

🤖 API designAdditionalArgs is a single string? that gets passed directly to args.Add(additionalArgs) in StartAvd. Since ProcessUtils.CreateProcessStartInfo uses ArgumentList on net5+ (no shell parsing), a string like ""-gpu swiftshader_indirect"" becomes one argument instead of two separate ones.

Consider changing this to IEnumerable<string>? so each argument is passed separately:

public IEnumerable<string>? AdditionalArgs { get; set; }

And in StartAvd:

if (options.AdditionalArgs != null)
    args.AddRange (options.AdditionalArgs);

This is safer and consistent with how ProcessUtils is designed to work.

Rule: Structured args, not string interpolation (Postmortem #50)

var adb = RequireAdb ();
using var stdout = new StringWriter ();
var psi = CreateAdbProcess (adb, "devices", "-l");
await ProcessUtils.StartProcess (psi, stdout, null, cancellationToken).ConfigureAwait (false);
Copy link
Member

Choose a reason for hiding this comment

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

🤖 Error handlingListDevicesAsync ignores the exit code returned by ProcessUtils.StartProcess. Compare with AvdManagerRunner.ListAvdsAsync which captures and checks it:

var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken)...;
if (exitCode != 0)
    throw new InvalidOperationException (...);

The same pattern should be applied here (and in GetShellPropertyAsync, RunShellCommandAsync, WaitForDeviceAsync, StopEmulatorAsync). Inconsistent exit-code checking creates a false sense of safety — if ListAvdsAsync checks, callers will assume ListDevicesAsync does too.

Rule: Check exit codes consistently (Postmortem #48)

this.getJdkPath = getJdkPath;
}

public string? AvdManagerPath {
Copy link
Member

Choose a reason for hiding this comment

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

🤖 Code duplication — This property reimplements the versioned cmdline-tools scanning (list dirs → parse versions → sort descending → try each → fallback to ""latest"" → fallback to legacy tools/bin) that ProcessUtils.FindCmdlineTool already provides. Both were added in this PR. Consider delegating:

public string? AvdManagerPath {
    get {
        var sdkPath = getSdkPath ();
        if (string.IsNullOrEmpty (sdkPath))
            return null;
        var ext = OS.IsWindows ? "".bat"" : """";
        return ProcessUtils.FindCmdlineTool (sdkPath, ""avdmanager"", ext);
    }
}

This removes ~20 lines and ensures both paths stay consistent if the search logic changes.

Rule: Reinventing the wheel / code duplication (Postmortem #40)

/// <summary>
/// Represents the type of an Android device.
/// </summary>
public enum AdbDeviceType
Copy link
Member

Choose a reason for hiding this comment

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

🤖 Code organization — This file contains three public types: AdbDeviceType (enum), AdbDeviceStatus (enum), and AdbDeviceInfo (class). Per the one-type-per-file convention, each should be in its own file: AdbDeviceType.cs, AdbDeviceStatus.cs, and AdbDeviceInfo.cs.

Rule: One type per file (Postmortem #9)

static string GetAvdRootDirectory ()
{
// ANDROID_AVD_HOME takes highest priority
var avdHome = Environment.GetEnvironmentVariable ("ANDROID_AVD_HOME");
Copy link
Member

Choose a reason for hiding this comment

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

🤖 Naming — Raw string ""ANDROID_AVD_HOME"" should use a constant from EnvironmentVariableNames (same as AndroidUserHome added in this PR). This prevents silent failures from typos and keeps all environment variable names centralized.

Consider adding public const string AndroidAvdHome = ""ANDROID_AVD_HOME""; to EnvironmentVariableNames and using it here.

Rule: Environment variable constants (Postmortem #19)

}
} catch (OperationCanceledException) {
throw;
} catch {
Copy link
Member

Choose a reason for hiding this comment

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

🤖 Error handling — Bare catch { } swallows all exceptions without capturing the Exception. Even though this failure is intentional (emulator may not support the command), capturing the exception enables diagnostic logging when issues surface:

} catch (Exception) {
    // Expected: emulator may not support 'emu avd name' command
}

At minimum, use catch (Exception) instead of bare catch so it's clear the intent is to catch CLR exceptions (not SEH exceptions on older runtimes).

Rule: No empty/bare catch blocks (Postmortem #11)

jonathanpeppers added a commit that referenced this pull request Mar 4, 2026
This skill let's you say:

    review this PR: #284

Some example code reviews:

* #283 (review)
* #284 (review)

This is built off a combination of previous code reviews, saved in
`docs/CODE_REVIEW_POSTMORTEM.md`, and the review rules in
`references/review-rules.md`.
rmarinho and others added 28 commits March 5, 2026 12:52
Port the adb device parsing, display name formatting, status mapping, and
emulator merging logic from dotnet/android's GetAvailableAndroidDevices
MSBuild task into the shared android-tools library.

Changes:
- AdbDeviceInfo: Add Type, Status, Description, AvdName, Product, TransportId
  fields using AdbDeviceType/AdbDeviceStatus enums
- AdbRunner.ParseAdbDevicesOutput: Regex-based parser matching dotnet/android
  (handles device/offline/unauthorized/no permissions states)
- AdbRunner.BuildDeviceDescription: Priority order AVD name > model > product >
  device > serial, with underscore-to-space cleanup
- AdbRunner.FormatDisplayName: Title case + API capitalization for AVD names
- AdbRunner.MapAdbStateToStatus: Maps adb states to AdbDeviceStatus enum
- AdbRunner.MergeDevicesAndEmulators: Merges running + non-running emulators,
  deduplicates by AVD name (case-insensitive), sorts online first
- AdbRunner.GetEmulatorAvdNameAsync: Queries AVD name via adb emu avd name
- AdbRunnerTests: 33 tests ported from dotnet/android test cases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The bare catch block was swallowing cancellation tokens, causing
cancelled operations to silently continue spawning adb processes
instead of propagating the cancellation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…riter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… add tests

- Replace manual ProcessStartInfo with ProcessUtils.CreateProcessStartInfo
- Use AndroidEnvironmentHelper.ConfigureEnvironment instead of duplicated method
- Extract ParseListAvdsOutput as internal static for testability
- Return IReadOnlyList<string> from ListAvdNamesAsync
- Add RequireEmulatorPath helper
- Add EmulatorRunnerTests (7 tests): parsing, path discovery, null/missing sdk

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- ListAvdsAsync(): List configured AVDs
- DeleteAvdAsync(): Delete an AVD

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add XML documentation to all public members
- Split AvdInfo into its own file (one type per file)
- Use 'is not null' instead of '!= null'
- Improve code formatting

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
- Add CreateAvdAsync with duplicate detection, orphaned AVD directory handling, stdin 'no' for hardware profile prompt
- Extract ParseAvdListOutput as internal static for testability
- 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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add 'using' to StringWriter instances in ListAvdsAsync and CreateAvdAsync
  to ensure proper disposal
- Add AvdManagerRunnerTests with 5 focused tests for ParseAvdListOutput:
  multiple AVDs, Windows newlines, empty output, no AVDs, missing device

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Convert AvdManagerRunner.cs and AvdInfo.cs to file-scoped namespaces
  per repo conventions for new files
- Remove accidentally tracked nupkg (already in .gitignore)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ironmentVariableNames, exit code checks

- AvdManagerPath: enumerate versioned cmdline-tools dirs descending
  (mirrors SdkManager.FindSdkManagerPath pattern), then latest, then
  legacy tools/bin
- Use ProcessUtils.CreateProcessStartInfo with separate args instead
  of building Arguments string manually (all 3 methods)
- Use EnvironmentVariableNames.AndroidHome/.JavaHome constants instead
  of hard-coded strings
- Check exit codes in ListAvdsAsync and DeleteAvdAsync, throw on failure
- Return IReadOnlyList<AvdInfo> from ListAvdsAsync
- Set Path on AvdInfo returned from CreateAvdAsync for consistency
- Extract RequireAvdManagerPath helper

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delegate to AndroidEnvironmentHelper.ConfigureEnvironment instead of inline setup
- Add 5 path discovery tests: versioned dir, higher version priority, latest fallback, null/missing sdk

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix argument validation: CreateAvdAsync/DeleteAvdAsync now throw
  ArgumentException for empty strings, ArgumentNullException for null,
  and validate before RequireAvdManagerPath()
- Make AndroidEnvironmentHelper internal, remove unused GetToolEnvironment
- Add ANDROID_USER_HOME to ConfigureEnvironment for consistent AVD
  location across tools (matches SdkManager behavior)
- Add AndroidUserHome constant to EnvironmentVariableNames
- Fix hard-coded AVD directory: use GetAvdRootDirectory() that respects
  ANDROID_AVD_HOME and ANDROID_USER_HOME env vars
- Re-list after CreateAvdAsync to get actual path from avdmanager
- Add 5 new validation tests for Create/DeleteAvdAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eTool

- ProcessUtils.ThrowIfFailed: consistent exit code checking with stderr/stdout context
- ProcessUtils.ValidateNotNullOrEmpty: reusable null/empty argument validation
- ProcessUtils.FindCmdlineTool: version-aware cmdline-tools directory search with legacy fallback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add BootAndWaitAsync to EmulatorRunner with multi-phase boot logic
- Add GetShellPropertyAsync and RunShellCommandAsync to AdbRunner
- Add EmulatorBootResult and EmulatorBootOptions model types
- Port unit tests from dotnet/android BootAndroidEmulatorTests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In netstandard2.0, string.IsNullOrEmpty lacks [NotNullWhen(false)],
so the compiler doesn't narrow string? after null checks. Add null-
forgiving operators where the preceding guard guarantees non-null.

Fixes: CS8601 in AndroidEnvironmentHelper.cs (jdkPath in Path.Combine)
Fixes: CS8620 in AdbRunner.cs (serial in string[] array literal)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds authorizing, recovery, sideload, bootloader, connecting, host
states and IgnoreCase flag to match the adb-runner branch regex.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Redirect stdout/stderr on the emulator process so it doesn't
inherit the caller's pipes. Without this, parent processes
(e.g. VS Code spawn with stdio:'pipe') never see the 'close'
event because the emulator holds the pipes open indefinitely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho force-pushed the feature/emulator-runner branch from ddfc763 to 5cbcc13 Compare March 5, 2026 12:53
…litting

- AdbRunner: check exit codes consistently in ListDevicesAsync, WaitForDeviceAsync,
  StopEmulatorAsync, GetShellPropertyAsync, RunShellCommandAsync
- AdbRunner: bare catch {} → catch (Exception) in GetEmulatorAvdNameAsync
- AdbDeviceInfo.cs: split 3 public types into separate files (AdbDeviceType.cs, AdbDeviceStatus.cs)
- EnvironmentVariableNames: add AndroidAvdHome constant, use in AvdManagerRunner
- AvdManagerRunner.AvdManagerPath: delegate to ProcessUtils.FindCmdlineTool (remove duplicate logic)
- EmulatorRunner.StartAvd: additionalArgs string? → IEnumerable<string>? for proper arg separation
- EmulatorRunner.StartAvd: set RedirectStandardOutput/Error=false to prevent pipe deadlock
- EmulatorBootOptions.AdditionalArgs: string? → IEnumerable<string>?

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