@@ -36,7 +36,7 @@ public class AdbRunner
3636 /// </summary>
3737 /// <param name="adbPath">Full path to the adb executable (e.g., "/path/to/sdk/platform-tools/adb").</param>
3838 /// <param name="environmentVariables">Optional environment variables to pass to adb processes.</param>
39- /// <param name="logger">Optional logger callback for diagnostic messages .</param>
39+ /// <param name="logger">Optional logger callback receiving a <see cref="TraceLevel"/> and message string .</param>
4040 public AdbRunner ( string adbPath , IDictionary < string , string > ? environmentVariables = null , Action < TraceLevel , string > ? logger = null )
4141 {
4242 if ( string . IsNullOrWhiteSpace ( adbPath ) )
@@ -48,7 +48,8 @@ public AdbRunner (string adbPath, IDictionary<string, string>? environmentVariab
4848
4949 /// <summary>
5050 /// Lists connected devices using 'adb devices -l'.
51- /// For emulators, queries the AVD name using 'adb -s <serial> emu avd name'.
51+ /// For online emulators, queries the AVD name via <c>getprop</c> / <c>emu avd name</c>.
52+ /// Offline emulators are included but without AVD names (querying them would fail).
5253 /// </summary>
5354 public virtual async Task < IReadOnlyList < AdbDeviceInfo > > ListDevicesAsync ( CancellationToken cancellationToken = default )
5455 {
@@ -61,11 +62,15 @@ public virtual async Task<IReadOnlyList<AdbDeviceInfo>> ListDevicesAsync (Cancel
6162
6263 var devices = ParseAdbDevicesOutput ( stdout . ToString ( ) . Split ( '\n ' ) ) ;
6364
64- // For each emulator, try to get the AVD name
65+ // For each online emulator, try to get the AVD name.
66+ // Skip offline emulators — neither getprop nor 'emu avd name' work on them
67+ // and attempting these commands causes unnecessary delays during boot polling.
6568 foreach ( var device in devices ) {
66- if ( device . Type == AdbDeviceType . Emulator ) {
69+ if ( device . Type == AdbDeviceType . Emulator && device . Status == AdbDeviceStatus . Online ) {
6770 device . AvdName = await GetEmulatorAvdNameAsync ( device . Serial , cancellationToken ) . ConfigureAwait ( false ) ;
6871 device . Description = BuildDeviceDescription ( device ) ;
72+ } else if ( device . Type == AdbDeviceType . Emulator ) {
73+ logger . Invoke ( TraceLevel . Verbose , $ "Skipping AVD name query for { device . Status } emulator { device . Serial } ") ;
6974 }
7075 }
7176
@@ -74,15 +79,26 @@ public virtual async Task<IReadOnlyList<AdbDeviceInfo>> ListDevicesAsync (Cancel
7479
7580 /// <summary>
7681 /// Queries the emulator for its AVD name.
77- /// Tries <c>adb -s <serial> emu avd name </c> first (emulator console protocol),
78- /// then falls back to <c>adb shell getprop ro.boot.qemu.avd_name </c> which reads the
79- /// boot property set by the emulator kernel . The fallback is needed because
80- /// <c>emu avd name</c> can return empty output on some adb/emulator version
81- /// combinations (observed with adb v36) .
82+ /// Tries <c>adb shell getprop ro.boot.qemu.avd_name </c> first (reliable on all modern
83+ /// emulators), then falls back to <c>adb -s <serial> emu avd name </c> (emulator
84+ /// console protocol) for older emulator versions . The <c>emu avd name</c> command returns
85+ /// empty output on emulator 36.4.10+ (observed with adb v36), so <c>getprop</c> is the
86+ /// preferred method .
8287 /// </summary>
8388 internal async Task < string ? > GetEmulatorAvdNameAsync ( string serial , CancellationToken cancellationToken = default )
8489 {
85- // Try 1: Console command (fast, works on most emulator versions)
90+ // Try 1: Shell property (reliable on modern emulators, always set by the emulator kernel)
91+ try {
92+ var avdName = await GetShellPropertyAsync ( serial , "ro.boot.qemu.avd_name" , cancellationToken ) . ConfigureAwait ( false ) ;
93+ if ( avdName is { Length : > 0 } name && ! string . IsNullOrWhiteSpace ( name ) )
94+ return name . Trim ( ) ;
95+ } catch ( OperationCanceledException ) {
96+ throw ;
97+ } catch ( Exception ex ) {
98+ logger . Invoke ( TraceLevel . Warning , $ "GetEmulatorAvdNameAsync: getprop failed for { serial } : { ex . Message } ") ;
99+ }
100+
101+ // Try 2: Console command (fallback for older emulators where getprop may not be available)
86102 try {
87103 using var stdout = new StringWriter ( ) ;
88104 var psi = ProcessUtils . CreateProcessStartInfo ( adbPath , "-s" , serial , "emu" , "avd" , "name" ) ;
@@ -97,19 +113,8 @@ public virtual async Task<IReadOnlyList<AdbDeviceInfo>> ListDevicesAsync (Cancel
97113 }
98114 } catch ( OperationCanceledException ) {
99115 throw ;
100- } catch {
101- // Fall through to getprop fallback
102- }
103-
104- // Try 2: Shell property (fallback when 'adb emu avd name' returns empty on some adb/emulator versions)
105- try {
106- var avdName = await GetShellPropertyAsync ( serial , "ro.boot.qemu.avd_name" , cancellationToken ) . ConfigureAwait ( false ) ;
107- if ( avdName is { Length : > 0 } name && ! string . IsNullOrWhiteSpace ( name ) )
108- return name . Trim ( ) ;
109- } catch ( OperationCanceledException ) {
110- throw ;
111- } catch {
112- // Both methods failed
116+ } catch ( Exception ex ) {
117+ logger . Invoke ( TraceLevel . Warning , $ "GetEmulatorAvdNameAsync: both methods failed for { serial } : { ex . Message } ") ;
113118 }
114119
115120 return null ;
0 commit comments