Skip to content

Use shared EmulatorRunner from android-tools for BootAndroidEmulator#10948

Closed
rmarinho wants to merge 1 commit intomainfrom
feature/use-shared-emulator-runner
Closed

Use shared EmulatorRunner from android-tools for BootAndroidEmulator#10948
rmarinho wants to merge 1 commit intomainfrom
feature/use-shared-emulator-runner

Conversation

@rmarinho
Copy link
Member

Summary

Replaces the 454-line BootAndroidEmulator MSBuild task with a ~180-line thin wrapper that delegates boot logic to EmulatorRunner.BootEmulatorAsync() from Xamarin.Android.Tools.AndroidSdk (shared via the external/xamarin-android-tools submodule).

This is the consumer PR that @jonathanpeppers requested on android-tools#284:

"Is there a dotnet/android PR that tests this API? I think the task is <BootAndroidEmulator/> we could replace existing code and call this instead."

What Changed

BootAndroidEmulator.cs (454 → ~180 lines)

Removed:

  • All process management (Process.Start, async output capture, CancelOutputRead/CancelErrorRead)
  • All polling logic (WaitForEmulatorOnline, WaitForFullBoot, Thread.Sleep loops)
  • All ADB interaction (IsOnlineAdbDevice, FindRunningEmulatorForAvd, GetRunningAvdName, GetShellProperty, RunShellCommand)
  • MonoAndroidHelper.RunProcess calls (6 occurrences)

Added:

  • Single ExecuteBoot() virtual method that creates AdbRunner + EmulatorRunner and calls BootEmulatorAsync()
  • ParseExtraArguments() to split EmulatorExtraArguments string → string[]
  • Error mapping: EmulatorBootResult.ErrorMessage → XA0143 (launch fail) / XA0145 (timeout/other)

Preserved:

  • Same MSBuild task interface (all inputs/outputs unchanged)
  • Same error codes (XA0143, XA0145) with same format strings
  • ResolveAdbPath() / ResolveEmulatorPath() logic unchanged
  • this.CreateTaskLogger() for MSBuild logging integration

BootAndroidEmulatorTests.cs (244 → ~250 lines, 9 → 10 tests)

New mock pattern: MockBootAndroidEmulator overrides ExecuteBoot() to return a canned EmulatorBootResult — much simpler than the previous approach of overriding 6 individual virtual methods.

Tests:

Test Validates
AlreadyOnlineDevice_PassesThrough Device already online → pass-through
AlreadyOnlinePhysicalDevice_PassesThrough Physical device serial → pass-through
AvdAlreadyRunning_WaitsForFullBoot AVD name → resolved serial
BootEmulator_AppearsAfterPolling New emulator → assigned serial
LaunchFailure_ReturnsError Launch fail → XA0143
BootTimeout_ReturnsError Timeout → XA0145
MultipleEmulators_FindsCorrectAvd Correct AVD resolution
ToolPaths_ResolvedFromAndroidSdkDirectory Path defaults
ExtraArguments_PassedToOptions ✨ NEW — extra args parsed
UnknownError_MapsToXA0145 ✨ NEW — fallback error code

Submodule Update

external/xamarin-android-tools updated from main (1a26c0c) → feature/emulator-runner (d8ee2d5)

API Used

From Xamarin.Android.Tools.AndroidSdk:

// EmulatorRunner — handles the full 3-phase boot flow
EmulatorRunner(string emulatorPath, logger: Action<TraceLevel, string>?)
EmulatorRunner.BootEmulatorAsync(string deviceOrAvdName, AdbRunner adbRunner, EmulatorBootOptions? options, CancellationToken)Task<EmulatorBootResult>

// EmulatorBootResult — success/fail + serial + error message
record EmulatorBootResult { bool Success; string? Serial; string? ErrorMessage; }

// EmulatorBootOptions — timeout, extra args, cold boot
class EmulatorBootOptions { TimeSpan BootTimeout; IEnumerable<string>? AdditionalArgs; bool ColdBoot; }

// AdbRunner — device listing, shell properties (used internally by BootEmulatorAsync)
AdbRunner(string adbPath, logger: Action<TraceLevel, string>?)

Dependencies

⚠️ Draft PR — depends on dotnet/android-tools#284 merging first. After it merges:

  1. Update submodule external/xamarin-android-tools to main
  2. Take this PR out of draft

How dotnet/android Benefits

This establishes EmulatorRunner as the shared emulator management API. The same API is also consumed by:

  • MAUI DevTools CLI (maui command) — for maui android emulator boot
  • VS Code MAUI extension — for one-click emulator management

Having a single shared implementation means bug fixes and improvements benefit all consumers automatically.

cc @jonathanpeppers

Replace the 454-line BootAndroidEmulator implementation with a thin
~180-line wrapper that delegates to EmulatorRunner.BootEmulatorAsync()
from Xamarin.Android.Tools.AndroidSdk.

Key changes:
- Remove all process management, polling, and boot detection logic
- Delegate to EmulatorRunner.BootEmulatorAsync() for the full 3-phase
  boot: check online → check AVD running → launch + poll + wait
- Map EmulatorBootResult errors to existing XA0143/XA0145 error codes
- Virtual ExecuteBoot() method for clean test mocking
- Update submodule to feature/emulator-runner (d8ee2d5)

Tests updated from 9 to 10 (added ExtraArguments and UnknownError tests)
using simplified mock pattern — MockBootAndroidEmulator overrides
ExecuteBoot() to return canned EmulatorBootResult values.

Depends on: dotnet/android-tools#284

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@rmarinho rmarinho closed this Mar 16, 2026
@rmarinho rmarinho deleted the feature/use-shared-emulator-runner branch March 16, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant