diff --git a/src/Xamarin.AndroidTools/AssemblyInfo.cs b/src/Xamarin.AndroidTools/AssemblyInfo.cs index 8f4867d5ff2..80bfd87ca30 100644 --- a/src/Xamarin.AndroidTools/AssemblyInfo.cs +++ b/src/Xamarin.AndroidTools/AssemblyInfo.cs @@ -30,3 +30,4 @@ "00240000048000009400000006020000002400005253413100040000010001008B63AA15D5222FA8E77F9DBFBE47AD82E74130B737743BC5" + "2A7074AD85D2EB34F28C83EFD0C1812C6957BAED24836300DF569EA0E6A71EEDA71C5E88A657AB8F243F268DA584B2D4EF4FCD3245B6A370" + "001A77DE21DE670E4678E29942F5628B153E88CD19504664FC663F9729DCA1CA2977875710EAF2CBD382DD6FD52BE9D3")] +[assembly: InternalsVisibleTo ("Xamarin.Android.Tools.AndroidSdk-Tests, PublicKey=0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf16cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b2c9733db")] diff --git a/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs b/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs index fea84e4156e..3519d6e6430 100644 --- a/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs +++ b/src/Xamarin.AndroidTools/Debugging/DebuggingExtensions.cs @@ -20,6 +20,8 @@ namespace Xamarin.AndroidTools.Debugging /// public static class DebuggingExtensions { + // Twenty retries at 250 ms intervals allow up to five seconds for the process to appear. + const int GET_PID_RETRY_COUNT = 20; const int WAIT_BEFORE_RETRY_GET_PID = 250; const int WAIT_FOR_DEBUGGER_TO_ATTACH_MS = 1400; @@ -58,14 +60,8 @@ public async static Task StartWithDebuggingAsync(this IAndroidDevice device, Exe } await androidDevice.ExecuteIntentCommandAsync(configuration.RunCommand, configuration.LogWiter, token).ConfigureAwait(false); - if (javaDebugging) { - try { - await androidDevice.ConnectJdwpAsync (configuration, token).ConfigureAwait(false); - } catch (Exception ex) { - if (configuration.LogWiter != null) - configuration.LogWiter ($"warning: Could not connect Jdwp. {ex}"); - } - } + if (javaDebugging) + await androidDevice.ConnectJdwpAsync (configuration, token).ConfigureAwait(false); } /// @@ -173,29 +169,27 @@ public static bool IsWSA(this AndroidDevice androidDevice) public static async Task ConnectJdwpAsync(this AndroidDevice androidDevice, ExecutionConfiguration config, CancellationToken token) { - if (config.RunCommand != null && config.RunCommand is AmStartCommand amStartCommand && amStartCommand.EnableDebugging) + if (config.RunCommand is AmStartCommand amStartCommand && amStartCommand.EnableDebugging) { - var packageName = (config.RunCommand as AmStartCommand).PackageName; - var pid = await androidDevice.GetProcessIDAsync(packageName, 5, WAIT_BEFORE_RETRY_GET_PID, token); + var packageName = amStartCommand.PackageName; + var pid = await androidDevice.GetProcessIDAsync (packageName, GET_PID_RETRY_COUNT, WAIT_BEFORE_RETRY_GET_PID, token).ConfigureAwait (false); if (pid <= 0) - { - throw new Exception("Process Not Found."); - } - - var jdwpClient = new JdwpClient(config.Debugger.JdwpHostName, config.Debugger.JdwpPort); + throw new InvalidOperationException ($"Could not find process for package '{packageName}'."); - await AdbServer.Default.ForwardPort(androidDevice, "tcp", jdwpClient.Port, "jdwp", pid, token); - try { - await jdwpClient.ConnectAsync (token); + using (var jdwpClient = new JdwpClient (config.Debugger.JdwpHostName, config.Debugger.JdwpPort)) { + await AdbServer.Default.ForwardPort (androidDevice, "tcp", jdwpClient.Port, "jdwp", pid, token); + try { + await jdwpClient.ConnectAsync (token); - // Keep the Connection for 1300 milliseconds, otherwise the Android OS ignores the connection! - // https://github.com/aosp-mirror/platform_frameworks_base/blob/6b28a227400749f4f8ad1f56799370e7c2cab149/core/java/android/os/Debug.java#L101C50-L101C54 - await Task.Delay (WAIT_FOR_DEBUGGER_TO_ATTACH_MS); + // Keep the Connection for 1300 milliseconds, otherwise the Android OS ignores the connection! + // https://github.com/aosp-mirror/platform_frameworks_base/blob/6b28a227400749f4f8ad1f56799370e7c2cab149/core/java/android/os/Debug.java#L101C50-L101C54 + await Task.Delay (WAIT_FOR_DEBUGGER_TO_ATTACH_MS, token); - await jdwpClient.DisconnectAsync (); - } finally { - await AdbServer.Default.KillForward (androidDevice, "tcp", jdwpClient.Port, token); + await jdwpClient.DisconnectAsync (); + } finally { + await AdbServer.Default.KillForward (androidDevice, "tcp", jdwpClient.Port, CancellationToken.None); + } } } } diff --git a/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs b/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs index fd8c99298d1..3a8a0a1071b 100644 --- a/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs +++ b/src/Xamarin.AndroidTools/Devices/AndroidDeviceExtensions.cs @@ -144,15 +144,20 @@ static Task KillProcessAndWaitForExitPreIcs (AndroidDevice device, string packag } public static async Task GetProcessIDAsync(this AndroidDevice device, string packageName, int maxAttempts, int timeBetweenAttempts, CancellationToken token) + { + return await GetProcessIDAsync (cancellationToken => device.GetProcessId (packageName, cancellationToken), maxAttempts, timeBetweenAttempts, token); + } + + internal static async Task GetProcessIDAsync (Func> getProcessId, int maxAttempts, int timeBetweenAttempts, CancellationToken token) { var retryCount = 1; - var pidOfResult = await device.GetProcessId(packageName, token); + var pidOfResult = await getProcessId (token); AndroidLogger.LogDebug("GetProcessIDAsync", "PID of the application :: " + pidOfResult); while (pidOfResult <= 0 && retryCount <= maxAttempts) { - await Task.Delay(timeBetweenAttempts); - pidOfResult = await device.GetProcessId(packageName, token); + await Task.Delay(timeBetweenAttempts, token); + pidOfResult = await getProcessId (token); AndroidLogger.LogDebug("GetProcessIDAsync", "Retrying " + retryCount + " time(s) to get PID of the application"); retryCount++; } diff --git a/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidDeviceExtensionsTests.cs b/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidDeviceExtensionsTests.cs new file mode 100644 index 00000000000..989aea79c85 --- /dev/null +++ b/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidDeviceExtensionsTests.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests; + +[TestFixture] +public class AndroidDeviceExtensionsTests +{ + [Test] + public async Task GetProcessIDAsyncRetriesUntilProcessAppears () + { + var processIds = new Queue (new [] { 0, 0, 1234 }); + + var processId = await AndroidDeviceExtensions.GetProcessIDAsync ( + _ => Task.FromResult (processIds.Dequeue ()), + maxAttempts: 20, + timeBetweenAttempts: 0, + token: CancellationToken.None + ); + + Assert.AreEqual (1234, processId); + Assert.AreEqual (0, processIds.Count); + } + + [Test] + public async Task GetProcessIDAsyncStopsAfterMaximumAttempts () + { + var attempts = 0; + + var processId = await AndroidDeviceExtensions.GetProcessIDAsync ( + _ => { + attempts++; + return Task.FromResult (0); + }, + maxAttempts: 2, + timeBetweenAttempts: 0, + token: CancellationToken.None + ); + + Assert.AreEqual (0, processId); + Assert.AreEqual (3, attempts); + } + + [Test] + public void GetProcessIDAsyncHonorsCancellation () + { + using (var cancellationTokenSource = new CancellationTokenSource ()) { + cancellationTokenSource.Cancel (); + + Assert.ThrowsAsync (() => AndroidDeviceExtensions.GetProcessIDAsync ( + _ => Task.FromResult (0), + maxAttempts: 20, + timeBetweenAttempts: 1, + token: cancellationTokenSource.Token + )); + } + } +} diff --git a/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index dc5364cb852..189fa0bbb78 100644 --- a/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -26,6 +26,7 @@ +