Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d184519
Add AndroidEnvironmentHelper for SDK tool environment setup
rmarinho Feb 24, 2026
b01f151
Convert AndroidEnvironmentHelper to file-scoped namespace, fix indent…
rmarinho Mar 3, 2026
92660a6
Add shared ConfigureEnvironment(PSI), use EnvironmentVariableNames co…
rmarinho Mar 3, 2026
31354d1
Add AdbRunner for adb CLI operations
rmarinho Feb 24, 2026
22369da
Refactor AdbRunner per copilot instructions
rmarinho Feb 24, 2026
b24cc45
Fix indentation: add tab indentation per Mono coding guidelines
rmarinho Feb 24, 2026
7aebf86
Add ANDROID_HOME environment and WaitForDeviceAsync
rmarinho Feb 25, 2026
f7b3c54
Trim verbose XML docs, make AdbPath internal
rmarinho Feb 26, 2026
053853a
Refactor AdbRunner: extract CreateAdbProcess helper, add serial valid…
rmarinho Feb 26, 2026
3325de5
Make AdbPath public for consumer access
rmarinho Feb 26, 2026
ba15cdd
Convert AdbRunner and AdbDeviceInfo to file-scoped namespaces, add us…
rmarinho Mar 3, 2026
6c85612
Use shared ConfigureEnvironment, ProcessUtils.CreateProcessStartInfo,…
rmarinho Mar 3, 2026
1277e33
Port device listing logic from dotnet/android GetAvailableAndroidDevices
rmarinho Mar 3, 2026
2359333
Make parsing/formatting methods public for dotnet/android consumption
rmarinho Mar 3, 2026
2c73419
Fix: re-throw OperationCanceledException in GetEmulatorAvdNameAsync
rmarinho Mar 3, 2026
d882132
Add EmulatorRunner for emulator CLI operations
rmarinho Feb 24, 2026
7e6a8f4
Refactor EmulatorRunner per copilot instructions
rmarinho Feb 24, 2026
1e44c6d
Fix indentation: add tab indentation per Mono coding guidelines
rmarinho Feb 24, 2026
8329a5a
Add JAVA_HOME and ANDROID_HOME environment support
rmarinho Feb 25, 2026
d83f05f
Trim verbose XML docs, make EmulatorPath internal
rmarinho Feb 26, 2026
ec5629a
Make EmulatorPath public for consumer access
rmarinho Feb 26, 2026
1367e3b
Convert EmulatorRunner to file-scoped namespace, add using on StringW…
rmarinho Mar 3, 2026
5842aee
Use shared ConfigureEnvironment, ProcessUtils.CreateProcessStartInfo,…
rmarinho Mar 3, 2026
ac6f37e
Add AvdManagerRunner for avdmanager CLI operations
rmarinho Feb 24, 2026
27491a4
Refactor AvdManagerRunner per copilot instructions
rmarinho Feb 24, 2026
9bce7b2
Fix indentation: add tab indentation per Mono coding guidelines
rmarinho Feb 24, 2026
05cd984
Add JAVA_HOME support, CreateAvdAsync, and orphaned AVD handling
rmarinho Feb 25, 2026
16bfba1
Trim verbose XML docs, make AvdManagerPath internal
rmarinho Feb 26, 2026
f06a07b
Make AvdManagerPath public for consumer access
rmarinho Feb 26, 2026
0e70fb0
Add using on StringWriters, add ParseAvdListOutput unit tests
rmarinho Mar 3, 2026
f749e9f
Convert to file-scoped namespaces, remove tracked nupkg
rmarinho Mar 3, 2026
8625a76
Address review: versioned path discovery, CreateProcessStartInfo, Env…
rmarinho Mar 3, 2026
702de9d
Use shared ConfigureEnvironment, add path discovery tests
rmarinho Mar 3, 2026
84a687d
Address PR #282 review feedback
rmarinho Mar 3, 2026
b5092a3
Remove unused using System.Collections.Generic
rmarinho Mar 3, 2026
cdc20dc
Add shared helpers: ThrowIfFailed, ValidateNotNullOrEmpty, FindCmdlin…
rmarinho Mar 3, 2026
71b8ad7
Port BootAndroidEmulator logic to shared EmulatorRunner
rmarinho Mar 4, 2026
c8c820a
Fix nullable reference type warnings for dotnet/android compatibility
rmarinho Mar 4, 2026
d0745eb
Align regex with adb-runner: explicit states + tab support
rmarinho Mar 4, 2026
0696fcc
Fix emulator stdout/stderr pipe inheritance in StartAvd
rmarinho Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,11 @@ internal static class EnvironmentVariableNames
/// Executable file extensions (Windows).
/// </summary>
public const string PathExt = "PATHEXT";

/// <summary>
/// Overrides the default location for Android user-specific data
/// (AVDs, preferences, etc.). Defaults to $HOME/.android.
/// </summary>
public const string AndroidUserHome = "ANDROID_USER_HOME";
}
}
84 changes: 84 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Xamarin.Android.Tools;

/// <summary>
/// Represents the type of an Android device.
/// </summary>
public enum AdbDeviceType
{
Device,
Emulator
}

/// <summary>
/// Represents the status of an Android device.
/// </summary>
public enum AdbDeviceStatus
{
Online,
Offline,
Unauthorized,
NoPermissions,
NotRunning,
Unknown
}

/// <summary>
/// Represents an Android device or emulator from 'adb devices -l' output.
/// Mirrors the metadata produced by dotnet/android's GetAvailableAndroidDevices task.
/// </summary>
public class AdbDeviceInfo
{
/// <summary>
/// Serial number of the device (e.g., "emulator-5554", "0A041FDD400327").
/// For non-running emulators, this is the AVD name.
/// </summary>
public string Serial { get; set; } = string.Empty;

/// <summary>
/// Human-friendly description of the device (e.g., "Pixel 7 API 35", "Pixel 6 Pro").
/// </summary>
public string Description { get; set; } = string.Empty;

/// <summary>
/// Device type: Device or Emulator.
/// </summary>
public AdbDeviceType Type { get; set; }

/// <summary>
/// Device status: Online, Offline, Unauthorized, NoPermissions, NotRunning, Unknown.
/// </summary>
public AdbDeviceStatus Status { get; set; }

/// <summary>
/// AVD name for emulators (e.g., "pixel_7_api_35"). Null for physical devices.
/// </summary>
public string? AvdName { get; set; }

/// <summary>
/// Device model from adb properties (e.g., "Pixel_6_Pro").
/// </summary>
public string? Model { get; set; }

/// <summary>
/// Product name from adb properties (e.g., "raven").
/// </summary>
public string? Product { get; set; }

/// <summary>
/// Device code name from adb properties (e.g., "raven").
/// </summary>
public string? Device { get; set; }

/// <summary>
/// Transport ID from adb properties.
/// </summary>
public string? TransportId { get; set; }

/// <summary>
/// Whether this device is an emulator.
/// </summary>
public bool IsEmulator => Type == AdbDeviceType.Emulator;
}
11 changes: 11 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Xamarin.Android.Tools;

public class AvdInfo
{
public string Name { get; set; } = string.Empty;
public string? DeviceProfile { get; set; }
public string? Path { get; set; }
}
18 changes: 18 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Xamarin.Android.Tools
{
/// <summary>
/// Options for booting an Android emulator.
/// </summary>
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);
}
}
15 changes: 15 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Xamarin.Android.Tools
{
/// <summary>
/// Result of an emulator boot operation.
/// </summary>
public class EmulatorBootResult
{
public bool Success { get; set; }
public string? Serial { get; set; }
public string? ErrorMessage { get; set; }
}
}
67 changes: 67 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,73 @@ static string JoinArguments (string[] args)
}
#endif

/// <summary>
/// Throws <see cref="InvalidOperationException"/> when <paramref name="exitCode"/> is non-zero.
/// Includes stderr/stdout context in the message when available.
/// </summary>
public static void ThrowIfFailed (int exitCode, string command, string? stderr = null, string? stdout = null)
{
if (exitCode == 0)
return;

var message = $"'{command}' failed with exit code {exitCode}.";

if (!string.IsNullOrEmpty (stderr))
message += $" stderr:{Environment.NewLine}{stderr!.Trim ()}";
if (!string.IsNullOrEmpty (stdout))
message += $" stdout:{Environment.NewLine}{stdout!.Trim ()}";

throw new InvalidOperationException (message);
}

/// <summary>
/// Validates that <paramref name="value"/> is not null or empty.
/// Throws <see cref="ArgumentNullException"/> for null values and
/// <see cref="ArgumentException"/> for empty strings.
/// </summary>
public static void ValidateNotNullOrEmpty (string? value, string paramName)
{
if (value is null)
throw new ArgumentNullException (paramName);
if (value.Length == 0)
throw new ArgumentException ("Value cannot be an empty string.", paramName);
}

/// <summary>
/// Searches versioned cmdline-tools directories (descending) and "latest" for a specific tool binary.
/// Falls back to the legacy tools/bin path. Returns null if not found.
/// </summary>
public static string? FindCmdlineTool (string sdkPath, string toolName, string extension)
{
var cmdlineToolsDir = Path.Combine (sdkPath, "cmdline-tools");

if (Directory.Exists (cmdlineToolsDir)) {
var subdirs = new List<(string name, Version? version)> ();
foreach (var dir in Directory.GetDirectories (cmdlineToolsDir)) {
var name = Path.GetFileName (dir);
if (string.IsNullOrEmpty (name) || name == "latest")
continue;
Version.TryParse (name, out var v);
subdirs.Add ((name, v ?? new Version (0, 0)));
}
subdirs.Sort ((a, b) => b.version!.CompareTo (a.version));

// Check versioned directories first (highest version first), then "latest"
foreach (var (name, _) in subdirs) {
var toolPath = Path.Combine (cmdlineToolsDir, name, "bin", toolName + extension);
if (File.Exists (toolPath))
return toolPath;
}
var latestPath = Path.Combine (cmdlineToolsDir, "latest", "bin", toolName + extension);
if (File.Exists (latestPath))
return latestPath;
}

// Legacy fallback: tools/bin/<tool>
var legacyPath = Path.Combine (sdkPath, "tools", "bin", toolName + extension);
return File.Exists (legacyPath) ? legacyPath : null;
}

internal static IEnumerable<string> FindExecutablesInPath (string executable)
{
var path = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? "";
Expand Down
Loading