Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
10da27d
Add AndroidEnvironmentHelper for SDK tool environment setup
rmarinho Feb 24, 2026
d1e0381
Convert AndroidEnvironmentHelper to file-scoped namespace, fix indent…
rmarinho Mar 3, 2026
64c31d5
Add shared ConfigureEnvironment(PSI), use EnvironmentVariableNames co…
rmarinho Mar 3, 2026
d5f23bd
Add AdbRunner for adb CLI operations
rmarinho Feb 24, 2026
53db30d
Refactor AdbRunner per copilot instructions
rmarinho Feb 24, 2026
bd51750
Fix indentation: add tab indentation per Mono coding guidelines
rmarinho Feb 24, 2026
d058501
Add ANDROID_HOME environment and WaitForDeviceAsync
rmarinho Feb 25, 2026
e7e4504
Trim verbose XML docs, make AdbPath internal
rmarinho Feb 26, 2026
da70f75
Refactor AdbRunner: extract CreateAdbProcess helper, add serial valid…
rmarinho Feb 26, 2026
8b0c25d
Make AdbPath public for consumer access
rmarinho Feb 26, 2026
1cab277
Convert AdbRunner and AdbDeviceInfo to file-scoped namespaces, add us…
rmarinho Mar 3, 2026
d3c4a78
Use shared ConfigureEnvironment, ProcessUtils.CreateProcessStartInfo,…
rmarinho Mar 3, 2026
d41ad12
Port device listing logic from dotnet/android GetAvailableAndroidDevices
rmarinho Mar 3, 2026
2c8d29c
Make parsing/formatting methods public for dotnet/android consumption
rmarinho Mar 3, 2026
95c02f1
Fix: re-throw OperationCanceledException in GetEmulatorAvdNameAsync
rmarinho Mar 3, 2026
a1df9f9
Add EmulatorRunner for emulator CLI operations
rmarinho Feb 24, 2026
852b1dc
Refactor EmulatorRunner per copilot instructions
rmarinho Feb 24, 2026
f6c49de
Fix indentation: add tab indentation per Mono coding guidelines
rmarinho Feb 24, 2026
9f2629c
Add JAVA_HOME and ANDROID_HOME environment support
rmarinho Feb 25, 2026
fe493f1
Trim verbose XML docs, make EmulatorPath internal
rmarinho Feb 26, 2026
3285c0a
Make EmulatorPath public for consumer access
rmarinho Feb 26, 2026
19e5f46
Convert EmulatorRunner to file-scoped namespace, add using on StringW…
rmarinho Mar 3, 2026
463acb6
Use shared ConfigureEnvironment, ProcessUtils.CreateProcessStartInfo,…
rmarinho Mar 3, 2026
5ecd99b
Add AvdManagerRunner for avdmanager CLI operations
rmarinho Feb 24, 2026
62a4858
Refactor AvdManagerRunner per copilot instructions
rmarinho Feb 24, 2026
77e8101
Fix indentation: add tab indentation per Mono coding guidelines
rmarinho Feb 24, 2026
dd654e8
Add JAVA_HOME support, CreateAvdAsync, and orphaned AVD handling
rmarinho Feb 25, 2026
a7f2865
Trim verbose XML docs, make AvdManagerPath internal
rmarinho Feb 26, 2026
def84fd
Make AvdManagerPath public for consumer access
rmarinho Feb 26, 2026
db6284c
Add using on StringWriters, add ParseAvdListOutput unit tests
rmarinho Mar 3, 2026
f19fc26
Convert to file-scoped namespaces, remove tracked nupkg
rmarinho Mar 3, 2026
e94bfbf
Address review: versioned path discovery, CreateProcessStartInfo, Env…
rmarinho Mar 3, 2026
758a612
Use shared ConfigureEnvironment, add path discovery tests
rmarinho Mar 3, 2026
620e530
Address PR #282 review feedback
rmarinho Mar 3, 2026
e818313
Remove unused using System.Collections.Generic
rmarinho Mar 3, 2026
f163274
Add shared helpers: ThrowIfFailed, ValidateNotNullOrEmpty, FindCmdlin…
rmarinho Mar 3, 2026
271b5c1
Use shared helpers in AvdManagerRunner
rmarinho Mar 3, 2026
37c3a79
Address PR #282 review feedback: exit codes, type splitting, structur…
rmarinho Mar 5, 2026
d96296a
Refactor AvdManagerRunner: resolved path constructor, pattern matchin…
rmarinho Mar 5, 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
12 changes: 12 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,17 @@ 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";

/// <summary>
/// Overrides the AVD storage directory.
/// Defaults to $ANDROID_USER_HOME/avd or $HOME/.android/avd.
/// </summary>
public const string AndroidAvdHome = "ANDROID_AVD_HOME";
}
}
62 changes: 62 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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 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;
}
17 changes: 17 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 status of an Android device.
/// </summary>
public enum AdbDeviceStatus
{
Online,
Offline,
Unauthorized,
NoPermissions,
NotRunning,
Unknown
}
13 changes: 13 additions & 0 deletions src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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
}
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; }
}
72 changes: 72 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,78 @@ 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>
internal 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 (stderr is { Length: > 0 })
message += $" stderr:{Environment.NewLine}{stderr.Trim ()}";
if (stdout is { Length: > 0 })
message += $" stdout:{Environment.NewLine}{stdout.Trim ()}";

throw new InvalidOperationException (message);
}

internal static void ThrowIfFailed (int exitCode, string command, StringWriter? stderr, StringWriter? stdout = null)
{
ThrowIfFailed (exitCode, command, stderr?.ToString (), stdout?.ToString ());
}

/// <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>
internal 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>
internal 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