Skip to content

Commit 73bd691

Browse files
[xabt] title case ComputeAvailableDevices
Context: dotnet/sdk#51337 Context: dotnet/sdk#51914 In 63f7cba, we added the `ComputeAvailableDevices` MSBuild target, which I was able to test end-to-end: D:\src\helloandroid> D:\src\dotnet\sdk\artifacts\bin\redist\Debug\dotnet\dotnet.exe run -bl Select a device to run on: > 0A041FDD400327 - Pixel 5 emulator-5554 - pixel 7 - api 36 Type to search Unfortunately, the AVD name is returned from `adb emu avd name`, which simply returns the property: > adb -s emulator-5554 shell getprop | grep avd_name [ro.boot.qemu.avd_name]: [pixel_7_-_api_36] We can call `TextInfo.ToTitleCase()`, replace underscores with spaces, and replace "Api" with "API" to make the AVD name more user-friendly. The only other alternative I considered was parsing the `~/.android/avd/<name>.ini` file to get the `displayname` property, but it would still require calling `adb emu avd name` to *get* the path to this `.ini` file. It felt more straightforward to just format the AVD name directly.
1 parent 29f8376 commit 73bd691

File tree

2 files changed

+140
-2
lines changed

2 files changed

+140
-2
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/GetAvailableAndroidDevices.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Globalization;
56
using System.Text.RegularExpressions;
67
using Microsoft.Android.Build.Tasks;
78
using Microsoft.Build.Framework;
@@ -24,6 +25,7 @@ enum DeviceType
2425
// Pattern to match device lines: <serial> <state> [key:value ...]
2526
// Example: emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64
2627
static readonly Regex AdbDevicesRegex = new(@"^([^\s]+)\s+(device|offline|unauthorized|no permissions)\s*(.*)$", RegexOptions.Compiled);
28+
static readonly Regex ApiRegex = new(@"\bApi\b", RegexOptions.Compiled);
2729

2830
readonly List<string> output = [];
2931

@@ -198,8 +200,7 @@ static string MapAdbStateToStatus (string adbState)
198200
var avdName = outputLines [0].Trim ();
199201
// Verify it's not the "OK" response
200202
if (!string.IsNullOrEmpty (avdName) && !avdName.Equals ("OK", StringComparison.OrdinalIgnoreCase)) {
201-
// Format the AVD name: replace underscores with spaces
202-
return avdName.Replace ('_', ' ');
203+
return FormatDisplayName(serial, avdName);
203204
}
204205
}
205206
} catch (Exception ex) {
@@ -208,4 +209,21 @@ static string MapAdbStateToStatus (string adbState)
208209

209210
return null;
210211
}
212+
213+
/// <summary>
214+
/// Formats the AVD name into a more user-friendly display name. Replace underscores with spaces and title case.
215+
/// </summary>
216+
public string FormatDisplayName(string serial, string avdName)
217+
{
218+
Log.LogDebugMessage ($"Emulator {serial}, original AVD name: {avdName}");
219+
220+
// Title case and replace underscores with spaces
221+
var textInfo = CultureInfo.InvariantCulture.TextInfo;
222+
avdName = textInfo.ToTitleCase(avdName.Replace ('_', ' '));
223+
224+
// Replace "Api" with "API"
225+
avdName = ApiRegex.Replace (avdName, "API");
226+
Log.LogDebugMessage ($"Emulator {serial}, formatted AVD display name: {avdName}");
227+
return avdName;
228+
}
211229
}

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GetAvailableAndroidDevicesTests.cs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,5 +436,125 @@ public void ParseAdbDaemonStarting ()
436436
Assert.AreEqual ("Device", physical.GetMetadata ("Type"));
437437
Assert.AreEqual ("Online", physical.GetMetadata ("Status"));
438438
}
439+
440+
[Test]
441+
public void FormatDisplayName_ReplacesUnderscoresWithSpaces ()
442+
{
443+
var task = new MockGetAvailableAndroidDevices {
444+
BuildEngine = engine,
445+
};
446+
447+
var result = task.FormatDisplayName ("emulator-5554", "pixel_7_pro");
448+
449+
Assert.AreEqual ("Pixel 7 Pro", result, "Should replace underscores with spaces");
450+
}
451+
452+
[Test]
453+
public void FormatDisplayName_AppliesTitleCase ()
454+
{
455+
var task = new MockGetAvailableAndroidDevices {
456+
BuildEngine = engine,
457+
};
458+
459+
var result = task.FormatDisplayName ("emulator-5554", "pixel 7 pro");
460+
461+
Assert.AreEqual ("Pixel 7 Pro", result, "Should apply title case");
462+
}
463+
464+
[Test]
465+
public void FormatDisplayName_ReplacesApiWithAPIUppercase ()
466+
{
467+
var task = new MockGetAvailableAndroidDevices {
468+
BuildEngine = engine,
469+
};
470+
471+
var result = task.FormatDisplayName ("emulator-5554", "pixel_5_api_34");
472+
473+
Assert.AreEqual ("Pixel 5 API 34", result, "Should replace 'Api' with 'API'");
474+
}
475+
476+
[Test]
477+
public void FormatDisplayName_HandlesMultipleApiOccurrences ()
478+
{
479+
var task = new MockGetAvailableAndroidDevices {
480+
BuildEngine = engine,
481+
};
482+
483+
var result = task.FormatDisplayName ("emulator-5554", "test_api_device_api_35");
484+
485+
Assert.AreEqual ("Test API Device API 35", result, "Should replace all 'Api' occurrences with 'API'");
486+
}
487+
488+
[Test]
489+
public void FormatDisplayName_HandlesMixedCaseInput ()
490+
{
491+
var task = new MockGetAvailableAndroidDevices {
492+
BuildEngine = engine,
493+
};
494+
495+
var result = task.FormatDisplayName ("emulator-5554", "PiXeL_7_API_35");
496+
497+
Assert.AreEqual ("Pixel 7 API 35", result, "Should normalize mixed case input");
498+
}
499+
500+
[Test]
501+
public void FormatDisplayName_HandlesComplexNames ()
502+
{
503+
var task = new MockGetAvailableAndroidDevices {
504+
BuildEngine = engine,
505+
};
506+
507+
var result = task.FormatDisplayName ("emulator-5554", "pixel_9_pro_xl_api_36");
508+
509+
Assert.AreEqual ("Pixel 9 Pro Xl API 36", result, "Should format complex names correctly");
510+
}
511+
512+
[Test]
513+
public void FormatDisplayName_PreservesNumbersAndSpecialChars ()
514+
{
515+
var task = new MockGetAvailableAndroidDevices {
516+
BuildEngine = engine,
517+
};
518+
519+
var result = task.FormatDisplayName ("emulator-5554", "pixel_7-pro_api_35");
520+
521+
Assert.AreEqual ("Pixel 7-Pro API 35", result, "Should preserve hyphens and numbers");
522+
}
523+
524+
[Test]
525+
public void FormatDisplayName_HandlesEmptyString ()
526+
{
527+
var task = new MockGetAvailableAndroidDevices {
528+
BuildEngine = engine,
529+
};
530+
531+
var result = task.FormatDisplayName ("emulator-5554", "");
532+
533+
Assert.AreEqual ("", result, "Should handle empty string");
534+
}
535+
536+
[Test]
537+
public void FormatDisplayName_HandlesSingleWord ()
538+
{
539+
var task = new MockGetAvailableAndroidDevices {
540+
BuildEngine = engine,
541+
};
542+
543+
var result = task.FormatDisplayName ("emulator-5554", "pixel");
544+
545+
Assert.AreEqual ("Pixel", result, "Should capitalize single word");
546+
}
547+
548+
[Test]
549+
public void FormatDisplayName_DoesNotReplaceApiInsideWords ()
550+
{
551+
var task = new MockGetAvailableAndroidDevices {
552+
BuildEngine = engine,
553+
};
554+
555+
var result = task.FormatDisplayName ("emulator-5554", "erapidevice");
556+
557+
Assert.AreEqual ("Erapidevice", result, "Should not replace 'api' when it's part of a larger word");
558+
}
439559
}
440560
}

0 commit comments

Comments
 (0)