Skip to content

Use shared EmulatorRunner from android-tools for BootAndroidEmulator#10949

Open
rmarinho wants to merge 1 commit intomainfrom
dev/rumar/use-shared-emulator-runner
Open

Use shared EmulatorRunner from android-tools for BootAndroidEmulator#10949
rmarinho wants to merge 1 commit intomainfrom
dev/rumar/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 marked this pull request as ready for review March 16, 2026 20:24
Copilot AI review requested due to automatic review settings March 16, 2026 20:24
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

Replaces the BootAndroidEmulator MSBuild task's inline process management with a thin wrapper around EmulatorRunner.BootEmulatorAsync() from the shared xamarin-android-tools library.

Changes:

  • Replaced ~270 lines of process/polling logic with delegation to EmulatorRunner/AdbRunner
  • Simplified test mock to override a single ExecuteBoot() method returning canned EmulatorBootResult
  • Updated external/xamarin-android-tools submodule to the feature/emulator-runner branch

Reviewed changes

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

File Description
BootAndroidEmulator.cs Replaced inline boot logic with EmulatorRunner.BootEmulatorAsync() delegation
BootAndroidEmulatorTests.cs Simplified mock and updated tests for new pattern
external/xamarin-android-tools Submodule update to include EmulatorRunner API

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +120 to +121
} else if (errorMessage.Contains ("Timed out")) {
Log.LogCodedError ("XA0145", Properties.Resources.XA0145, Device, BootTimeoutSeconds);
Comment on lines +118 to +120
if (errorMessage.Contains ("Failed to launch")) {
Log.LogCodedError ("XA0143", Properties.Resources.XA0143, Device, errorMessage);
} else if (errorMessage.Contains ("Timed out")) {
Comment on lines +183 to +185
public void ExtraArguments_PassedToOptions ()
{
string[]? capturedArgs = null;
Comment on lines +142 to +143
return emulatorRunner.BootEmulatorAsync (device, adbRunner, options)
.GetAwaiter ().GetResult ();
Copy link
Member

Choose a reason for hiding this comment

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

This should be async/await. We can:

  • Change base class to AndroidAsyncTask
  • Override RunTaskAsync instead
  • Everything in this file becomes async

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.

3 participants