Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -0,0 +1,9 @@
// 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 a hardware device profile (e.g., "pixel_7", "Nexus 5X") from <c>avdmanager list device</c>.
/// </summary>
public record AvdDeviceProfile (string Id, string Name, string? Oem = null, string? Tag = null);
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,14 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy
virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AvdDeviceProfile!>!>!
Xamarin.Android.Tools.AvdDeviceProfile
Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id, string! Name, string? Oem = null, string? Tag = null) -> void
Xamarin.Android.Tools.AvdDeviceProfile.Id.get -> string!
Xamarin.Android.Tools.AvdDeviceProfile.Id.init -> void
Xamarin.Android.Tools.AvdDeviceProfile.Name.get -> string!
Xamarin.Android.Tools.AvdDeviceProfile.Name.init -> void
Xamarin.Android.Tools.AvdDeviceProfile.Oem.get -> string?
Xamarin.Android.Tools.AvdDeviceProfile.Oem.init -> void
Xamarin.Android.Tools.AvdDeviceProfile.Tag.get -> string?
Xamarin.Android.Tools.AvdDeviceProfile.Tag.init -> void
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,14 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy
virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<System.Collections.Generic.IReadOnlyList<Xamarin.Android.Tools.AvdDeviceProfile!>!>!
Xamarin.Android.Tools.AvdDeviceProfile
Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id, string! Name, string? Oem = null, string? Tag = null) -> void
Xamarin.Android.Tools.AvdDeviceProfile.Id.get -> string!
Xamarin.Android.Tools.AvdDeviceProfile.Id.init -> void
Xamarin.Android.Tools.AvdDeviceProfile.Name.get -> string!
Xamarin.Android.Tools.AvdDeviceProfile.Name.init -> void
Xamarin.Android.Tools.AvdDeviceProfile.Oem.get -> string?
Xamarin.Android.Tools.AvdDeviceProfile.Oem.init -> void
Xamarin.Android.Tools.AvdDeviceProfile.Tag.get -> string?
Xamarin.Android.Tools.AvdDeviceProfile.Tag.init -> void
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,63 @@ public async Task DeleteAvdAsync (string name, CancellationToken cancellationTok
ProcessUtils.ThrowIfFailed (exitCode, $"avdmanager delete avd --name {name}", stderr);
}

/// <summary>
/// Lists available device profiles (hardware definitions) using <c>avdmanager list device</c>.
/// </summary>
public async Task<IReadOnlyList<AvdDeviceProfile>> ListDeviceProfilesAsync (CancellationToken cancellationToken = default)
{
using var stdout = new StringWriter ();
using var stderr = new StringWriter ();
var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, "list", "device");
logger.Invoke (TraceLevel.Verbose, "Running: avdmanager list device");
var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false);

ProcessUtils.ThrowIfFailed (exitCode, "avdmanager list device", stderr, stdout);

return ParseDeviceListOutput (stdout.ToString ());
}

internal static IReadOnlyList<AvdDeviceProfile> ParseDeviceListOutput (string output)
{
var profiles = new List<AvdDeviceProfile> ();
string? currentId = null, currentName = null, currentOem = null, currentTag = null;

foreach (var line in output.Split ('\n')) {
var trimmed = line.Trim ();
if (trimmed.StartsWith ("id:", StringComparison.OrdinalIgnoreCase)) {
if (currentId is not null)
profiles.Add (new AvdDeviceProfile (currentId, currentName ?? currentId, currentOem, currentTag));
// Parse: id: 0 or "automotive_1024p_landscape"
currentOem = currentTag = null;
currentName = null;
var orIndex = trimmed.IndexOf (" or ", StringComparison.Ordinal);
if (orIndex >= 0) {
var rawId = trimmed.Substring (orIndex + 4).Trim ().Trim ('"');
currentId = rawId;
} else {
currentId = trimmed.Substring (3).Trim ().Trim ('"');
}
}
else if (trimmed.StartsWith ("Name:", StringComparison.OrdinalIgnoreCase))
currentName = trimmed.Substring (5).Trim ();
else if (trimmed.StartsWith ("OEM", StringComparison.OrdinalIgnoreCase)) {
var colonIndex = trimmed.IndexOf (':');
if (colonIndex >= 0)
currentOem = trimmed.Substring (colonIndex + 1).Trim ();
}
else if (trimmed.StartsWith ("Tag", StringComparison.OrdinalIgnoreCase)) {
var colonIndex = trimmed.IndexOf (':');
if (colonIndex >= 0)
currentTag = trimmed.Substring (colonIndex + 1).Trim ();
}
}

if (currentId is not null)
profiles.Add (new AvdDeviceProfile (currentId, currentName ?? currentId, currentOem, currentTag));

return profiles;
}

internal static IReadOnlyList<AvdInfo> ParseAvdListOutput (string output)
{
var avds = new List<AvdInfo> ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,4 +293,97 @@ public void FindCmdlineTool_PrefersStableOverPreRelease ()
Directory.Delete (tempDir, true);
}
}

// --- ParseDeviceListOutput tests ---

[Test]
public void ParseDeviceListOutput_MultipleProfiles ()
{
var output =
"Available devices definitions:\n" +
"id: 0 or \"automotive_1024p_landscape\"\n" +
" Name: Automotive (1024p landscape)\n" +
" OEM : Google\n" +
" Tag : android-automotive-playstore\n" +
"---------\n" +
"id: 1 or \"pixel_7\"\n" +
" Name: Pixel 7\n" +
" OEM : Google\n" +
"---------\n" +
"id: 2 or \"Nexus 5X\"\n" +
" Name: Nexus 5X\n" +
" OEM : Google\n";

var profiles = AvdManagerRunner.ParseDeviceListOutput (output);

Assert.AreEqual (3, profiles.Count);

Assert.AreEqual ("automotive_1024p_landscape", profiles [0].Id);
Assert.AreEqual ("Automotive (1024p landscape)", profiles [0].Name);
Assert.AreEqual ("Google", profiles [0].Oem);
Assert.AreEqual ("android-automotive-playstore", profiles [0].Tag);

Assert.AreEqual ("pixel_7", profiles [1].Id);
Assert.AreEqual ("Pixel 7", profiles [1].Name);
Assert.AreEqual ("Google", profiles [1].Oem);
Assert.IsNull (profiles [1].Tag);

Assert.AreEqual ("Nexus 5X", profiles [2].Id);
Assert.AreEqual ("Nexus 5X", profiles [2].Name);
}

[Test]
public void ParseDeviceListOutput_EmptyOutput ()
{
var profiles = AvdManagerRunner.ParseDeviceListOutput ("");
Assert.AreEqual (0, profiles.Count);
}

[Test]
public void ParseDeviceListOutput_WindowsNewlines ()
{
var output =
"Available devices definitions:\r\n" +
"id: 0 or \"pixel_fold\"\r\n" +
" Name: Pixel Fold\r\n" +
" OEM : Google\r\n" +
" Tag : default\r\n";

var profiles = AvdManagerRunner.ParseDeviceListOutput (output);

Assert.AreEqual (1, profiles.Count);
Assert.AreEqual ("pixel_fold", profiles [0].Id);
Assert.AreEqual ("Pixel Fold", profiles [0].Name);
Assert.AreEqual ("Google", profiles [0].Oem);
Assert.AreEqual ("default", profiles [0].Tag);
}

[Test]
public void ParseDeviceListOutput_NoNameFallsBackToId ()
{
var output =
"id: 0 or \"custom_device\"\n" +
" OEM : SomeOem\n";

var profiles = AvdManagerRunner.ParseDeviceListOutput (output);

Assert.AreEqual (1, profiles.Count);
Assert.AreEqual ("custom_device", profiles [0].Id);
Assert.AreEqual ("custom_device", profiles [0].Name);
}

[Test]
public void ParseDeviceListOutput_HeaderOnly ()
{
var output = "Available devices definitions:\n";
var profiles = AvdManagerRunner.ParseDeviceListOutput (output);
Assert.AreEqual (0, profiles.Count);
}

[Test]
public void ParseDeviceListOutput_ReturnsIReadOnlyList ()
{
var profiles = AvdManagerRunner.ParseDeviceListOutput ("");
Assert.IsInstanceOf<IReadOnlyList<AvdDeviceProfile>> (profiles);
}
}