66using System . Diagnostics ;
77using System . Globalization ;
88using System . IO ;
9- using System . Linq ;
109using System . Net . Sockets ;
1110using System . Text . RegularExpressions ;
1211using System . Threading ;
@@ -20,52 +19,28 @@ namespace Xamarin.Android.Tools;
2019/// </summary>
2120public class AdbRunner
2221{
23- readonly Func < string ? > getSdkPath ;
24- readonly Func < string ? > ? getJdkPath ;
22+ readonly string adbPath ;
23+ readonly IDictionary < string , string > ? environmentVariables ;
2524
2625 // Pattern to match device lines: <serial> <state> [key:value ...]
27- // Requires 2+ spaces between serial and state (adb pads serials).
28- // Matches known adb device states. Uses \s+ to handle both space and tab separators.
26+ // Uses \s+ to handle both space and tab separators.
2927 // Explicit state list prevents false positives from non-device lines.
3028 static readonly Regex AdbDevicesRegex = new Regex (
3129 @"^([^\s]+)\s+(device|offline|unauthorized|authorizing|no permissions|recovery|sideload|bootloader|connecting|host)\s*(.*)$" ,
3230 RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
3331 static readonly Regex ApiRegex = new Regex ( @"\bApi\b" , RegexOptions . Compiled ) ;
3432
35- public AdbRunner ( Func < string ? > getSdkPath )
36- : this ( getSdkPath , null )
37- {
38- }
39-
40- public AdbRunner ( Func < string ? > getSdkPath , Func < string ? > ? getJdkPath )
41- {
42- this . getSdkPath = getSdkPath ?? throw new ArgumentNullException ( nameof ( getSdkPath ) ) ;
43- this . getJdkPath = getJdkPath ;
44- }
45-
46- public string ? AdbPath {
47- get {
48- var sdkPath = getSdkPath ( ) ;
49- if ( ! string . IsNullOrEmpty ( sdkPath ) ) {
50- var ext = OS . IsWindows ? ".exe" : "" ;
51- var sdkAdb = Path . Combine ( sdkPath , "platform-tools" , "adb" + ext ) ;
52- if ( File . Exists ( sdkAdb ) )
53- return sdkAdb ;
54- }
55- return ProcessUtils . FindExecutablesInPath ( "adb" ) . FirstOrDefault ( ) ;
56- }
57- }
58-
59- public bool IsAvailable => AdbPath is not null ;
60-
61- string RequireAdb ( )
62- {
63- return AdbPath ?? throw new InvalidOperationException ( "ADB not found." ) ;
64- }
65-
66- IDictionary < string , string > GetEnvironmentVariables ( )
33+ /// <summary>
34+ /// Creates a new AdbRunner with the full path to the adb executable.
35+ /// </summary>
36+ /// <param name="adbPath">Full path to the adb executable (e.g., "/path/to/sdk/platform-tools/adb").</param>
37+ /// <param name="environmentVariables">Optional environment variables to pass to adb processes.</param>
38+ public AdbRunner ( string adbPath , IDictionary < string , string > ? environmentVariables = null )
6739 {
68- return AndroidEnvironmentHelper . GetEnvironmentVariables ( getSdkPath ( ) , getJdkPath ? . Invoke ( ) ) ;
40+ if ( string . IsNullOrWhiteSpace ( adbPath ) )
41+ throw new ArgumentException ( "Path to adb must not be empty." , nameof ( adbPath ) ) ;
42+ this . adbPath = adbPath ;
43+ this . environmentVariables = environmentVariables ;
6944 }
7045
7146 /// <summary>
@@ -74,12 +49,10 @@ IDictionary<string, string> GetEnvironmentVariables ()
7449 /// </summary>
7550 public async Task < IReadOnlyList < AdbDeviceInfo > > ListDevicesAsync ( CancellationToken cancellationToken = default )
7651 {
77- var adb = RequireAdb ( ) ;
78- var envVars = GetEnvironmentVariables ( ) ;
7952 using var stdout = new StringWriter ( ) ;
8053 using var stderr = new StringWriter ( ) ;
81- var psi = ProcessUtils . CreateProcessStartInfo ( adb , "devices" , "-l" ) ;
82- var exitCode = await ProcessUtils . StartProcess ( psi , stdout , stderr , cancellationToken , envVars ) . ConfigureAwait ( false ) ;
54+ var psi = ProcessUtils . CreateProcessStartInfo ( adbPath , "devices" , "-l" ) ;
55+ var exitCode = await ProcessUtils . StartProcess ( psi , stdout , stderr , cancellationToken , environmentVariables ) . ConfigureAwait ( false ) ;
8356
8457 ProcessUtils . ThrowIfFailed ( exitCode , "adb devices -l" , stderr . ToString ( ) ) ;
8558
@@ -88,7 +61,7 @@ public async Task<IReadOnlyList<AdbDeviceInfo>> ListDevicesAsync (CancellationTo
8861 // For each emulator, try to get the AVD name
8962 foreach ( var device in devices ) {
9063 if ( device . Type == AdbDeviceType . Emulator ) {
91- device . AvdName = await GetEmulatorAvdNameAsync ( adb , device . Serial , cancellationToken ) . ConfigureAwait ( false ) ;
64+ device . AvdName = await GetEmulatorAvdNameAsync ( device . Serial , cancellationToken ) . ConfigureAwait ( false ) ;
9265 device . Description = BuildDeviceDescription ( device ) ;
9366 }
9467 }
@@ -101,13 +74,12 @@ public async Task<IReadOnlyList<AdbDeviceInfo>> ListDevicesAsync (CancellationTo
10174 /// falling back to a direct emulator console TCP query if that fails.
10275 /// Ported from dotnet/android GetAvailableAndroidDevices.GetEmulatorAvdName.
10376 /// </summary>
104- internal async Task < string ? > GetEmulatorAvdNameAsync ( string adbPath , string serial , CancellationToken cancellationToken = default )
77+ internal async Task < string ? > GetEmulatorAvdNameAsync ( string serial , CancellationToken cancellationToken = default )
10578 {
10679 try {
107- var envVars = GetEnvironmentVariables ( ) ;
10880 using var stdout = new StringWriter ( ) ;
10981 var psi = ProcessUtils . CreateProcessStartInfo ( adbPath , "-s" , serial , "emu" , "avd" , "name" ) ;
110- await ProcessUtils . StartProcess ( psi , stdout , null , cancellationToken , envVars ) . ConfigureAwait ( false ) ;
82+ await ProcessUtils . StartProcess ( psi , stdout , null , cancellationToken , environmentVariables ) . ConfigureAwait ( false ) ;
11183
11284 foreach ( var line in stdout . ToString ( ) . Split ( '\n ' ) ) {
11385 var trimmed = line . Trim ( ) ;
@@ -187,14 +159,11 @@ public async Task WaitForDeviceAsync (string? serial = null, TimeSpan? timeout =
187159 if ( effectiveTimeout <= TimeSpan . Zero )
188160 throw new ArgumentOutOfRangeException ( nameof ( timeout ) , effectiveTimeout , "Timeout must be a positive value." ) ;
189161
190- var adb = RequireAdb ( ) ;
191- var envVars = GetEnvironmentVariables ( ) ;
192-
193162 var args = string . IsNullOrEmpty ( serial )
194163 ? new [ ] { "wait-for-device" }
195- : new [ ] { "-s" , serial ! , "wait-for-device" } ;
164+ : new [ ] { "-s" , serial , "wait-for-device" } ;
196165
197- var psi = ProcessUtils . CreateProcessStartInfo ( adb , args ) ;
166+ var psi = ProcessUtils . CreateProcessStartInfo ( adbPath , args ) ;
198167
199168 using var cts = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
200169 cts . CancelAfter ( effectiveTimeout ) ;
@@ -203,7 +172,7 @@ public async Task WaitForDeviceAsync (string? serial = null, TimeSpan? timeout =
203172 using var stderr = new StringWriter ( ) ;
204173
205174 try {
206- var exitCode = await ProcessUtils . StartProcess ( psi , stdout , stderr , cts . Token , envVars ) . ConfigureAwait ( false ) ;
175+ var exitCode = await ProcessUtils . StartProcess ( psi , stdout , stderr , cts . Token , environmentVariables ) . ConfigureAwait ( false ) ;
207176 ProcessUtils . ThrowIfFailed ( exitCode , "adb wait-for-device" , stderr . ToString ( ) , stdout . ToString ( ) ) ;
208177 } catch ( OperationCanceledException ) when ( ! cancellationToken . IsCancellationRequested ) {
209178 throw new TimeoutException ( $ "Timed out waiting for device after { effectiveTimeout . TotalSeconds } s.") ;
@@ -215,11 +184,9 @@ public async Task StopEmulatorAsync (string serial, CancellationToken cancellati
215184 if ( string . IsNullOrWhiteSpace ( serial ) )
216185 throw new ArgumentException ( "Serial must not be empty." , nameof ( serial ) ) ;
217186
218- var adb = RequireAdb ( ) ;
219- var envVars = GetEnvironmentVariables ( ) ;
220- var psi = ProcessUtils . CreateProcessStartInfo ( adb , "-s" , serial , "emu" , "kill" ) ;
187+ var psi = ProcessUtils . CreateProcessStartInfo ( adbPath , "-s" , serial , "emu" , "kill" ) ;
221188 using var stderr = new StringWriter ( ) ;
222- var exitCode = await ProcessUtils . StartProcess ( psi , null , stderr , cancellationToken , envVars ) . ConfigureAwait ( false ) ;
189+ var exitCode = await ProcessUtils . StartProcess ( psi , null , stderr , cancellationToken , environmentVariables ) . ConfigureAwait ( false ) ;
223190 ProcessUtils . ThrowIfFailed ( exitCode , $ "adb -s { serial } emu kill", stderr . ToString ( ) ) ;
224191 }
225192
0 commit comments