diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs index ef87d743..c95bcf51 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs @@ -6,10 +6,10 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Xunit; namespace AdvancedSharpAdbClient.Tests @@ -326,6 +326,9 @@ public async void ExecuteRemoteCommandAsyncUnresponsiveTest() /// Tests the method. /// [Fact] +#if WINDOWS + [SupportedOSPlatform("windows")] +#endif public async void GetFrameBufferAsyncTest() { string[] requests = @@ -341,8 +344,8 @@ public async void GetFrameBufferAsyncTest() NoSyncRequests, NoSyncResponses, [ - await File.ReadAllBytesAsync("Assets/framebufferheader.bin"), - await File.ReadAllBytesAsync("Assets/framebuffer.bin") + await File.ReadAllBytesAsync("Assets/FramebufferHeader.bin"), + await File.ReadAllBytesAsync("Assets/Framebuffer.bin") ], null, () => TestClient.GetFrameBufferAsync(Device)); @@ -400,7 +403,7 @@ public async void RunLogServiceAsyncTest() ConsoleOutputReceiver receiver = new(); - await using FileStream stream = File.OpenRead("Assets/logcat.bin"); + await using FileStream stream = File.OpenRead("Assets/Logcat.bin"); await using ShellStream shellStream = new(stream, false); List logs = []; Action sink = logs.Add; @@ -637,43 +640,36 @@ public async void UnrootAsyncTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void InstallAsyncTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install' -S 205774" - ]; - // The app data is sent in chunks of 32 kb List applicationDataChunks = []; - await using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) { - while (true) + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) { - byte[] buffer = new byte[32 * 1024]; - int read = await stream.ReadAsync(buffer); - - if (read == 0) - { - break; - } - else - { - buffer = buffer.Take(read).ToArray(); - applicationDataChunks.Add(buffer); - } + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); } } byte[] response = "Success\n"u8.ToArray(); - await using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) { + string[] requests = + [ + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install' -S {stream.Length}" + ]; + await RunTestAsync( OkResponses(2), NoResponseMessages, @@ -681,825 +677,290 @@ await RunTestAsync( NoSyncRequests, NoSyncResponses, [response], - applicationDataChunks.ToArray(), - () => TestClient.InstallAsync(Device, stream)); + applicationDataChunks, + () => TestClient.InstallAsync(Device, stream, + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); } } /// - /// Tests the method. - /// - [Fact] - public async void InstallCreateAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install-create' -p com.google.android.gms" - ]; - - byte[] streamData = Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n"); - await using MemoryStream shellStream = new(streamData); - - string session = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.InstallCreateAsync(Device, "com.google.android.gms")); - - Assert.Equal("936013062", session); - } - - /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void InstallWriteAsyncTest() + public async void InstallMultipleAsyncTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install-write' -S 205774 936013062 base.apk" - ]; - // The app data is sent in chunks of 32 kb List applicationDataChunks = []; - await using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + await using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk")) { - while (true) + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) { - byte[] buffer = new byte[32 * 1024]; - int read = await stream.ReadAsync(buffer); - - if (read == 0) - { - break; - } - else - { - buffer = buffer.Take(read).ToArray(); - applicationDataChunks.Add(buffer); - } + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); } } - byte[] response = "Success: streamed 205774 bytes\n"u8.ToArray(); - - await using (FileStream stream = File.OpenRead("Assets/testapp.apk")) - { - await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - NoSyncRequests, - NoSyncResponses, - [response], - applicationDataChunks.ToArray(), - () => TestClient.InstallWriteAsync(Device, stream, "base", "936013062")); - } - } + await using FileStream abiStream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk"); - /// - /// Tests the method. - /// - [Fact] - public async void InstallCommitAsyncTest() - { string[] requests = [ + "host:transport:169.254.109.177:5555", + "exec:cmd package 'install-create' -p com.google.android.gms", + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {abiStream.Length} 936013062 splitAPK0.apk", "host:transport:169.254.109.177:5555", "exec:cmd package 'install-commit' 936013062" ]; - byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); - await using MemoryStream shellStream = new(streamData); - - await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.InstallCommitAsync(Device, "936013062")); - } - - /// - /// Tests the method. - /// - [Fact] - public async void UninstallAsyncTest() - { - string[] requests = + byte[][] responses = [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'uninstall' com.android.gallery3d" + Encoding.ASCII.GetBytes($"Success: streamed {abiStream.Length} bytes\n") ]; - byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); - using MemoryStream shellStream = new(streamData); + await using MemoryStream sessionStream = new(Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n")); + await using MemoryStream commitStream = new("Success\n"u8.ToArray()); await RunTestAsync( - OkResponses(2), + OkResponses(6), NoResponseMessages, requests, - [shellStream], - () => TestClient.UninstallAsync(Device, "com.android.gallery3d")); - } - - /// - /// Tests the method. - /// - [Fact] - public async void GetFeatureSetAsyncTest() - { - string[] requests = ["host-serial:169.254.109.177:5555:features"]; - string[] responses = ["sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2\r\n"]; - - string[] features = await RunTestAsync( - OkResponse, + NoSyncRequests, + NoSyncResponses, responses, - requests, - async () => await TestClient.GetFeatureSetAsync(Device).ToArrayAsync()); - - Assert.Equal(12, features.Length); - Assert.Equal("sendrecv_v2_brotli", features.FirstOrDefault()); - Assert.Equal("stat_v2", features.LastOrDefault()); + applicationDataChunks, + [sessionStream, commitStream], + () => TestClient.InstallMultipleAsync(Device, [abiStream], "com.google.android.gms", + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.CreateSession, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void DumpScreenStringAsyncTest() + public async void InstallMultipleWithBaseAsyncTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - string cleanDump = File.ReadAllText(@"Assets/dumpscreen_clean.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - await using MemoryStream shellStream = new(streamData); - - string xml = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.DumpScreenStringAsync(Device)); - - Assert.Equal(cleanDump, xml); - } - - /// - /// Tests the method. - /// - [Fact] - public async void DumpScreenStringAsyncMIUITest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string miuiDump = File.ReadAllText(@"Assets/dumpscreen_miui.txt"); - string cleanMIUIDump = File.ReadAllText(@"Assets/dumpscreen_miui_clean.txt"); - byte[] miuiStreamData = Encoding.UTF8.GetBytes(miuiDump); - await using MemoryStream miuiStream = new(miuiStreamData); - - string miuiXml = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [miuiStream], - () => TestClient.DumpScreenStringAsync(Device)); + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; - Assert.Equal(cleanMIUIDump, miuiXml); - } + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; - /// - /// Tests the method. - /// - [Fact] - public async void DumpScreenStringAsyncEmptyTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - await using MemoryStream emptyStream = new(); + await using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; - string emptyXml = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [emptyStream], - () => TestClient.DumpScreenStringAsync(Device)); + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - Assert.True(string.IsNullOrEmpty(emptyXml)); - } + await using FileStream baseStream = File.OpenRead("Assets/TestApp/base.apk"); + await using FileStream abiStream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk"); - /// - /// Tests the method. - /// - [Fact] - public async void DumpScreenStringAsyncErrorTest() - { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string errorXml = File.ReadAllText(@"Assets/dumpscreen_error.txt"); - byte[] errorStreamData = Encoding.UTF8.GetBytes(errorXml); - await using MemoryStream errorStream = new(errorStreamData); - - await Assert.ThrowsAsync(() => - RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [errorStream], - () => TestClient.DumpScreenStringAsync(Device))); - } - - /// - /// Tests the method. - /// - [Fact] - public async void DumpScreenAsyncTest() - { - string[] requests = - [ + "exec:cmd package 'install-create'", "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - await using MemoryStream shellStream = new(streamData); - - XmlDocument xml = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.DumpScreenAsync(Device)); - - string cleanDump = File.ReadAllText(@"Assets/dumpscreen_clean.txt"); - XmlDocument doc = new(); - doc.LoadXml(cleanDump); - - Assert.Equal(doc, xml); - } - -#if WINDOWS10_0_17763_0_OR_GREATER - /// - /// Tests the method. - /// - [Fact] - public async void DumpScreenWinRTAsyncTest() - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {baseStream.Length} 936013062 baseAPK.apk", "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - await using MemoryStream shellStream = new(streamData); - - Windows.Data.Xml.Dom.XmlDocument xml = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.DumpScreenWinRTAsync(Device)); - - string cleanDump = File.ReadAllText(@"Assets/dumpscreen_clean.txt"); - Windows.Data.Xml.Dom.XmlDocument doc = new(); - doc.LoadXml(cleanDump); - - Assert.Equal(doc.InnerText, xml.InnerText); - } - -#endif - - /// - /// Tests the method. - /// - [Fact] - public async void ClickAsyncTest() - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {abiStream.Length} 936013062 splitAPK0.apk", "host:transport:169.254.109.177:5555", - "shell:input tap 100 100" + "exec:cmd package 'install-commit' 936013062" ]; - byte[] streamData = @"java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission - at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) - at android.os.Parcel.createException(Parcel.java:2357) - at android.os.Parcel.readException(Parcel.java:2340) - at android.os.Parcel.readException(Parcel.java:2282) - at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) - at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) - at com.android.commands.input.Input.injectMotionEvent(Input.java:397) - at com.android.commands.input.Input.access$200(Input.java:41) - at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) - at com.android.commands.input.Input$InputTap.run(Input.java:217) - at com.android.commands.input.Input.onRun(Input.java:107) - at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) - at com.android.commands.input.Input.main(Input.java:71) - at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) - at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) -Caused by: android.os.RemoteException: Remote stack trace: - at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) - at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) - at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) - at android.os.Binder.execTransactInternal(Binder.java:1165) - at android.os.Binder.execTransact(Binder.java:1134)"u8.ToArray(); - await using MemoryStream shellStream = new(streamData); - - JavaException exception = await Assert.ThrowsAsync(() => - RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.ClickAsync(Device, 100, 100))); - - Assert.Equal("SecurityException", exception.JavaName); - Assert.Equal("Injecting to another application requires INJECT_EVENTS permission", exception.Message); - Assert.Equal(@" at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) - at android.os.Parcel.createException(Parcel.java:2357) - at android.os.Parcel.readException(Parcel.java:2340) - at android.os.Parcel.readException(Parcel.java:2282) - at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) - at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) - at com.android.commands.input.Input.injectMotionEvent(Input.java:397) - at com.android.commands.input.Input.access$200(Input.java:41) - at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) - at com.android.commands.input.Input$InputTap.run(Input.java:217) - at com.android.commands.input.Input.onRun(Input.java:107) - at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) - at com.android.commands.input.Input.main(Input.java:71) - at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) - at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) -Caused by: android.os.RemoteException: Remote stack trace: - at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) - at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) - at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) - at android.os.Binder.execTransactInternal(Binder.java:1165) - at android.os.Binder.execTransact(Binder.java:1134)", exception.JavaStackTrace, ignoreLineEndingDifferences: true); - } - - /// - /// Tests the method. - /// - [Fact] - public async void ClickCordsAsyncTest() - { - string[] requests = + byte[][] responses = [ - "host:transport:169.254.109.177:5555", - "shell:input tap 100 100" + Encoding.ASCII.GetBytes($"Success: streamed {baseStream.Length} bytes\n"), + Encoding.ASCII.GetBytes($"Success: streamed {abiStream.Length} bytes\n") ]; - byte[] streamData = "Error: Injecting to another application requires INJECT_EVENTS permission\r\n"u8.ToArray(); - await using MemoryStream shellStream = new(streamData); - - _ = await Assert.ThrowsAsync(() => - RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.ClickAsync(Device, new Point(100, 100)))); - } - - /// - /// Tests the method. - /// - [Fact] - public async void SwipeAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input swipe 100 200 300 400 500" - ]; - - await using MemoryStream shellStream = new(); + using MemoryStream sessionStream = new(Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n")); + using MemoryStream commitStream = new("Success\n"u8.ToArray()); await RunTestAsync( - OkResponses(2), + OkResponses(8), NoResponseMessages, requests, - [shellStream], - () => TestClient.SwipeAsync(Device, 100, 200, 300, 400, 500)); + NoSyncRequests, + NoSyncResponses, + responses, + applicationDataChunks, + [sessionStream, commitStream], + () => TestClient.InstallMultipleAsync(Device, baseStream, [abiStream], + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.CreateSession, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void SwipeAsyncElementTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input swipe 100 200 300 400 500" - ]; - - await using MemoryStream shellStream = new(); - - await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.SwipeAsync(Device, new Element(TestClient, Device, new Rectangle(0, 0, 200, 400)), new Element(TestClient, Device, new Rectangle(0, 0, 600, 800)), 500)); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("21216 27761\r\n", true)] - [InlineData(" 21216 27761\r\n", true)] - [InlineData(" \r\n", false)] - [InlineData("\r\n", false)] - [InlineData(" ", false)] - [InlineData("", false)] - public async void IsAppRunningAsyncTest(string response, bool expected) - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:pidof com.google.android.gms" - ]; - - byte[] streamData = Encoding.UTF8.GetBytes(response); - await using MemoryStream shellStream = new(streamData); - - bool result = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.IsAppRunningAsync(Device, "com.google.android.gms")); - - Assert.Equal(expected, result); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("app.lawnchair", true)] - [InlineData("com.android.settings", true)] - [InlineData("com.google.android.gms", false)] - public async void IsAppInForegroundAsyncTest(string packageName, bool expected) - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:dumpsys activity activities | grep mResumedActivity" - ]; - - byte[] streamData = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} - mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"u8.ToArray(); - await using MemoryStream shellStream = new(streamData); - - bool result = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.IsAppInForegroundAsync(Device, packageName)); - - Assert.Equal(expected, result); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("com.google.android.gms", "21216 27761\r\n", AppStatus.Background)] - [InlineData("com.android.gallery3d", "\r\n", AppStatus.Stopped)] - public async void GetAppStatusAsyncTest(string packageName, string response, AppStatus expected) - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:dumpsys activity activities | grep mResumedActivity", - "host:transport:169.254.109.177:5555", - $"shell:pidof {packageName}" - ]; - - byte[] activityData = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} - mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"u8.ToArray(); - await using MemoryStream activityStream = new(activityData); - byte[] pidData = Encoding.UTF8.GetBytes(response); - await using MemoryStream pidStream = new(pidData); - - AppStatus result = await RunTestAsync( - OkResponses(4), - NoResponseMessages, - requests, - [activityStream, pidStream], - () => TestClient.GetAppStatusAsync(Device, packageName)); - - Assert.Equal(expected, result); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("app.lawnchair", AppStatus.Foreground)] - [InlineData("com.android.settings", AppStatus.Foreground)] - public async void GetAppStatusAsyncForegroundTest(string packageName, AppStatus expected) + public async void InstallCreateAsyncTest() { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:dumpsys activity activities | grep mResumedActivity" + "exec:cmd package 'install-create' -p com.google.android.gms" ]; - byte[] streamData = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} - mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"u8.ToArray(); + byte[] streamData = Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n"); await using MemoryStream shellStream = new(streamData); - AppStatus result = await RunTestAsync( + string session = await RunTestAsync( OkResponses(2), NoResponseMessages, requests, [shellStream], - () => TestClient.GetAppStatusAsync(Device, packageName)); - - Assert.Equal(expected, result); - } - - /// - /// Tests the method. - /// - [Fact] - public async void FindElementAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - await using MemoryStream shellStream = new(streamData); + () => TestClient.InstallCreateAsync(Device, "com.google.android.gms")); - Element element = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.FindElementAsync(Device)); - - Assert.Equal(144, element.GetChildCount()); - Element child = element[0][0][0][0][0][0][0][0][2][1][0][0]; - Assert.Equal("where-where", child.Text); - Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), child.Bounds); - Assert.Equal(child, element.FindDescendantOrSelf(x => x.Text == "where-where")); - Assert.Equal(2, element.FindDescendants().Where(x => x.Text == "where-where").Count()); + Assert.Equal("936013062", session); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void FindElementsAsyncTest() + public async void InstallWriteAsyncTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - await using MemoryStream shellStream = new(streamData); + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; - Element[] elements = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - async () => await TestClient.FindElementsAsync(Device).ToArrayAsync()); - - int childCount = elements.Length; - Array.ForEach(elements, x => childCount += x.GetChildCount()); - Assert.Equal(145, childCount); - Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; - Assert.Equal("where-where", element.Attributes["text"]); - Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), element.Bounds); - } + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - /// - /// Tests the method. - /// - [Fact] - public async void FindAsyncElementsTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + string[] requests = + [ + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {stream.Length} 936013062 base.apk" + ]; - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - await using MemoryStream shellStream = new(streamData); + byte[] response = Encoding.ASCII.GetBytes($"Success: streamed {stream.Length} bytes\n"); - List elements = await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - async () => + double temp = 0; + Progress progress = new(); + progress.ProgressChanged += (sender, args) => { - List elements = []; - await foreach (Element element in TestClient.FindAsyncElements(Device)) - { - elements.Add(element); - } - return elements; - }); - - int childCount = elements.Count; - elements.ForEach(x => childCount += x.GetChildCount()); - Assert.Equal(145, childCount); - Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; - Assert.Equal("where-where", element.Attributes["text"]); - Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), element.Bounds); - } + Assert.True(temp <= args, $"{nameof(args)}: {args} is less than {temp}."); + temp = args; + }; - /// - /// Tests the method. - /// - [Fact] - public async void SendKeyEventAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_MOVE_END" - ]; - - await using MemoryStream shellStream = new(); - - await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.SendKeyEventAsync(Device, "KEYCODE_MOVE_END")); + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + requests, + NoSyncRequests, + NoSyncResponses, + [response], + applicationDataChunks, + () => TestClient.InstallWriteAsync(Device, stream, "base", "936013062", progress)); + } } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void SendTextAsyncTest() + public async void InstallCommitAsyncTest() { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:input text Hello, World", + "exec:cmd package 'install-commit' 936013062" ]; - await using MemoryStream shellStream = new(); + byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); + await using MemoryStream shellStream = new(streamData); await RunTestAsync( OkResponses(2), NoResponseMessages, requests, [shellStream], - () => TestClient.SendTextAsync(Device, "Hello, World")); - } - - /// - /// Tests the method. - /// - [Fact] - public async void ClearInputAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_MOVE_END", - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL" - ]; - - await using MemoryStream firstShellStream = new(); - await using MemoryStream secondShellStream = new(); - - await RunTestAsync( - OkResponses(4), - NoResponseMessages, - requests, - [firstShellStream, secondShellStream], - () => TestClient.ClearInputAsync(Device, 3)); - } - - /// - /// Tests the method. - /// - [Fact] - public async void StartAppAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:monkey -p com.android.settings 1", - ]; - - await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - () => TestClient.StartAppAsync(Device, "com.android.settings")); - } - - /// - /// Tests the method. - /// - [Fact] - public async void StopAppAsyncTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:am force-stop com.android.settings", - ]; - - await RunTestAsync( - OkResponses(2), - NoResponseMessages, - requests, - () => TestClient.StopAppAsync(Device, "com.android.settings")); + () => TestClient.InstallCommitAsync(Device, "936013062")); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void ClickBackButtonAsyncTest() + public async void UninstallAsyncTest() { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_BACK" + "exec:cmd package 'uninstall' com.android.gallery3d" ]; - await using MemoryStream shellStream = new(); + byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); + using MemoryStream shellStream = new(streamData); await RunTestAsync( OkResponses(2), NoResponseMessages, requests, [shellStream], - () => TestClient.ClickBackButtonAsync(Device)); + () => TestClient.UninstallAsync(Device, "com.android.gallery3d")); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public async void ClickHomeButtonAsyncTest() + public async void GetFeatureSetAsyncTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_HOME" - ]; - - await using MemoryStream shellStream = new(); + string[] requests = ["host-serial:169.254.109.177:5555:features"]; + string[] responses = ["sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2\r\n"]; - await RunTestAsync( - OkResponses(2), - NoResponseMessages, + string[] features = await RunTestAsync( + OkResponse, + responses, requests, - [shellStream], - () => TestClient.ClickHomeButtonAsync(Device)); + async () => await TestClient.GetFeatureSetAsync(Device).ToArrayAsync()); + + Assert.Equal(12, features.Length); + Assert.Equal("sendrecv_v2_brotli", features.FirstOrDefault()); + Assert.Equal("stat_v2", features.LastOrDefault()); } private Task RunConnectAsyncTest(Func test, string connectString) diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.cs index bff1ee48..754f3841 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.cs @@ -6,8 +6,8 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.Versioning; using System.Text; -using System.Xml; using Xunit; namespace AdvancedSharpAdbClient.Tests @@ -437,6 +437,9 @@ public void CreateRefreshableFramebufferTest() } [Fact] +#if WINDOWS + [SupportedOSPlatform("windows")] +#endif public void GetFrameBufferTest() { string[] requests = @@ -452,8 +455,8 @@ public void GetFrameBufferTest() NoSyncRequests, NoSyncResponses, [ - File.ReadAllBytes("Assets/framebufferheader.bin"), - File.ReadAllBytes("Assets/framebuffer.bin") + File.ReadAllBytes("Assets/FramebufferHeader.bin"), + File.ReadAllBytes("Assets/Framebuffer.bin") ], null, () => TestClient.GetFrameBuffer(Device)); @@ -508,7 +511,7 @@ public void RunLogServiceTest() ConsoleOutputReceiver receiver = new(); - using FileStream stream = File.OpenRead("Assets/logcat.bin"); + using FileStream stream = File.OpenRead("Assets/Logcat.bin"); using ShellStream shellStream = new(stream, false); List logs = []; Action sink = logs.Add; @@ -745,43 +748,36 @@ public void UnrootTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void InstallTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install' -S 205774" - ]; - // The app data is sent in chunks of 32 kb List applicationDataChunks = []; - using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) { - while (true) - { - byte[] buffer = new byte[32 * 1024]; - int read = stream.Read(buffer.AsSpan(0, buffer.Length)); + byte[] buffer = new byte[32 * 1024]; + int read = 0; - if (read == 0) - { - break; - } - else - { - buffer = buffer.Take(read).ToArray(); - applicationDataChunks.Add(buffer); - } + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); } } byte[] response = "Success\n"u8.ToArray(); - using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) { + string[] requests = + [ + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install' -S {stream.Length}" + ]; + RunTest( OkResponses(2), NoResponseMessages, @@ -789,791 +785,314 @@ public void InstallTest() NoSyncRequests, NoSyncResponses, [response], - applicationDataChunks.ToArray(), - () => TestClient.Install(Device, stream)); + applicationDataChunks, + () => TestClient.Install(Device, stream, + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); } } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public void InstallCreateTest() + public void InstallMultipleTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install-create' -p com.google.android.gms" - ]; - - byte[] streamData = Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n"); - using MemoryStream shellStream = new(streamData); - - string session = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.InstallCreate(Device, "com.google.android.gms")); - - Assert.Equal("936013062", session); - } - - /// - /// Tests the method. - /// - [Fact] - public void InstallWriteTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install-write' -S 205774 936013062 base.apk" - ]; - // The app data is sent in chunks of 32 kb List applicationDataChunks = []; - using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk")) { - while (true) - { - byte[] buffer = new byte[32 * 1024]; - int read = stream.Read(buffer.AsSpan(0, buffer.Length)); + byte[] buffer = new byte[32 * 1024]; + int read = 0; - if (read == 0) - { - break; - } - else - { - buffer = buffer.Take(read).ToArray(); - applicationDataChunks.Add(buffer); - } + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); } } - byte[] response = "Success: streamed 205774 bytes\n"u8.ToArray(); - - using (FileStream stream = File.OpenRead("Assets/testapp.apk")) + using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.xxhdpi.apk")) { - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - NoSyncRequests, - NoSyncResponses, - [response], - applicationDataChunks.ToArray(), - () => TestClient.InstallWrite(Device, stream, "base", "936013062")); - } - } - - /// - /// Tests the method. - /// - [Fact] - public void InstallCommitTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "exec:cmd package 'install-commit' 936013062" - ]; + byte[] buffer = new byte[32 * 1024]; + int read = 0; - byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); - using MemoryStream shellStream = new(streamData); + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.InstallCommit(Device, "936013062")); - } + using FileStream abiStream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk"); + using FileStream dpiStream = File.OpenRead("Assets/TestApp/split_config.xxhdpi.apk"); - /// - /// Tests the method. - /// - [Fact] - public void UninstallTest() - { string[] requests = [ "host:transport:169.254.109.177:5555", - "exec:cmd package 'uninstall' com.android.gallery3d" - ]; - - byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); - using MemoryStream shellStream = new(streamData); - - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.Uninstall(Device, "com.android.gallery3d")); - } - - /// - /// Tests the method. - /// - [Fact] - public void GetFeatureSetTest() - { - string[] requests = ["host-serial:169.254.109.177:5555:features"]; - string[] responses = ["sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2\r\n"]; - - string[] features = RunTest( - OkResponse, - responses, - requests, - () => TestClient.GetFeatureSet(Device).ToArray()); - - Assert.Equal(12, features.Length); - Assert.Equal("sendrecv_v2_brotli", features.FirstOrDefault()); - Assert.Equal("stat_v2", features.LastOrDefault()); - } - - /// - /// Tests the method. - /// - [Fact] - public void DumpScreenStringTest() - { - string[] requests = - [ + "exec:cmd package 'install-create' -p com.google.android.gms", "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - string cleanDump = File.ReadAllText(@"Assets/dumpscreen_clean.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - using MemoryStream shellStream = new(streamData); - - string xml = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.DumpScreenString(Device)); - - Assert.Equal(cleanDump, xml); - } - - /// - /// Tests the method. - /// - [Fact] - public void DumpScreenStringMIUITest() - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {abiStream.Length} 936013062 splitAPK0.apk", "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string miuidump = File.ReadAllText(@"Assets/dumpscreen_miui.txt"); - string cleanMIUIDump = File.ReadAllText(@"Assets/dumpscreen_miui_clean.txt"); - byte[] miuiStreamData = Encoding.UTF8.GetBytes(miuidump); - using MemoryStream miuiStream = new(miuiStreamData); - - string miuiXml = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [miuiStream], - () => TestClient.DumpScreenString(Device)); - - Assert.Equal(cleanMIUIDump, miuiXml); - } - - /// - /// Tests the method. - /// - [Fact] - public void DumpScreenStringEmptyTest() - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {dpiStream.Length} 936013062 splitAPK1.apk", "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" + "exec:cmd package 'install-commit' 936013062" ]; - byte[] emptyStreamData = Encoding.UTF8.GetBytes(string.Empty); - using MemoryStream emptyStream = new(emptyStreamData); - - string emptyXml = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [emptyStream], - () => TestClient.DumpScreenString(Device)); - - Assert.True(string.IsNullOrEmpty(emptyXml)); - } - - /// - /// Tests the method. - /// - [Fact] - public void DumpScreenStringErrorTest() - { - string[] requests = + byte[][] responses = [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" + Encoding.ASCII.GetBytes($"Success: streamed {abiStream.Length} bytes\n"), + Encoding.ASCII.GetBytes($"Success: streamed {dpiStream.Length} bytes\n") ]; - string errorXml = File.ReadAllText(@"Assets/dumpscreen_error.txt"); - byte[] errorStreamData = Encoding.UTF8.GetBytes(errorXml); - using MemoryStream errorStream = new(errorStreamData); + using MemoryStream sessionStream = new(Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n")); + using MemoryStream commitStream = new("Success\n"u8.ToArray()); - Assert.Throws(() => RunTest( - OkResponses(2), + OkResponses(8), NoResponseMessages, requests, - [errorStream], - () => TestClient.DumpScreenString(Device))); - } - - /// - /// Tests the method. - /// - [Fact] - public void DumpScreenTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - using MemoryStream shellStream = new(streamData); - - XmlDocument xml = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.DumpScreen(Device)); - - string cleanDump = File.ReadAllText(@"Assets/dumpscreen_clean.txt"); - XmlDocument doc = new(); - doc.LoadXml(cleanDump); - - Assert.Equal(doc, xml); + NoSyncRequests, + NoSyncResponses, + responses, + applicationDataChunks, + [sessionStream, commitStream], + () => TestClient.InstallMultiple(Device, [abiStream, dpiStream], "com.google.android.gms", + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.CreateSession, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); } -#if WINDOWS10_0_17763_0_OR_GREATER /// - /// Tests the method. + /// Tests the method. /// [Fact] - public void DumpScreenWinRTTest() + public void InstallMultipleWithBaseTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; - - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - using MemoryStream shellStream = new(streamData); + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; - Windows.Data.Xml.Dom.XmlDocument xml = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.DumpScreenWinRT(Device)); + using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; - string cleanDump = File.ReadAllText(@"Assets/dumpscreen_clean.txt"); - Windows.Data.Xml.Dom.XmlDocument doc = new(); - doc.LoadXml(cleanDump); + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - Assert.Equal(doc.InnerText, xml.InnerText); - } + using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; -#endif + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - /// - /// Tests the method. - /// - [Fact] - public void ClickTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input tap 100 100" - ]; + using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.xxhdpi.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; - byte[] streamData = @"java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission - at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) - at android.os.Parcel.createException(Parcel.java:2357) - at android.os.Parcel.readException(Parcel.java:2340) - at android.os.Parcel.readException(Parcel.java:2282) - at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) - at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) - at com.android.commands.input.Input.injectMotionEvent(Input.java:397) - at com.android.commands.input.Input.access$200(Input.java:41) - at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) - at com.android.commands.input.Input$InputTap.run(Input.java:217) - at com.android.commands.input.Input.onRun(Input.java:107) - at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) - at com.android.commands.input.Input.main(Input.java:71) - at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) - at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) -Caused by: android.os.RemoteException: Remote stack trace: - at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) - at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) - at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) - at android.os.Binder.execTransactInternal(Binder.java:1165) - at android.os.Binder.execTransact(Binder.java:1134)"u8.ToArray(); - using MemoryStream shellStream = new(streamData); + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - JavaException exception = Assert.Throws(() => - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.Click(Device, 100, 100))); - - Assert.Equal("SecurityException", exception.JavaName); - Assert.Equal("Injecting to another application requires INJECT_EVENTS permission", exception.Message); - Assert.Equal(@" at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) - at android.os.Parcel.createException(Parcel.java:2357) - at android.os.Parcel.readException(Parcel.java:2340) - at android.os.Parcel.readException(Parcel.java:2282) - at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) - at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) - at com.android.commands.input.Input.injectMotionEvent(Input.java:397) - at com.android.commands.input.Input.access$200(Input.java:41) - at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) - at com.android.commands.input.Input$InputTap.run(Input.java:217) - at com.android.commands.input.Input.onRun(Input.java:107) - at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) - at com.android.commands.input.Input.main(Input.java:71) - at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) - at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) -Caused by: android.os.RemoteException: Remote stack trace: - at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) - at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) - at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) - at android.os.Binder.execTransactInternal(Binder.java:1165) - at android.os.Binder.execTransact(Binder.java:1134)", exception.JavaStackTrace, ignoreLineEndingDifferences: true); - } + using FileStream baseStream = File.OpenRead("Assets/TestApp/base.apk"); + using FileStream abiStream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk"); + using FileStream dpiStream = File.OpenRead("Assets/TestApp/split_config.xxhdpi.apk"); - /// - /// Tests the method. - /// - [Fact] - public void ClickCordsTest() - { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:input tap 100 100" - ]; - - byte[] streamData = "Error: Injecting to another application requires INJECT_EVENTS permission\r\n"u8.ToArray(); - using MemoryStream shellStream = new(streamData); - - _ = Assert.Throws(() => - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.Click(Device, new Point(100, 100)))); - } - - /// - /// Tests the method. - /// - [Fact] - public void SwipeTest() - { - string[] requests = - [ + "exec:cmd package 'install-create'", "host:transport:169.254.109.177:5555", - "shell:input swipe 100 200 300 400 500" - ]; - - using MemoryStream shellStream = new(); - - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.Swipe(Device, 100, 200, 300, 400, 500)); - } - - /// - /// Tests the method. - /// - [Fact] - public void SwipeElementTest() - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {baseStream.Length} 936013062 baseAPK.apk", "host:transport:169.254.109.177:5555", - "shell:input swipe 100 200 300 400 500" - ]; - - using MemoryStream shellStream = new(); - - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.Swipe(Device, new Element(TestClient, Device, new Rectangle(0, 0, 200, 400)), new Element(TestClient, Device, new Rectangle(0, 0, 600, 800)), 500)); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("21216 27761\r\n", true)] - [InlineData(" 21216 27761\r\n", true)] - [InlineData("12836\r\n", true)] - [InlineData(" \r\n", false)] - [InlineData("\r\n", false)] - [InlineData(" ", false)] - [InlineData("", false)] - public void IsAppRunningTest(string response, bool expected) - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {abiStream.Length} 936013062 splitAPK0.apk", "host:transport:169.254.109.177:5555", - "shell:pidof com.google.android.gms" - ]; - - byte[] streamData = Encoding.UTF8.GetBytes(response); - using MemoryStream shellStream = new(streamData); - - bool result = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.IsAppRunning(Device, "com.google.android.gms")); - - Assert.Equal(expected, result); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("app.lawnchair", true)] - [InlineData("com.android.settings", true)] - [InlineData("com.google.android.gms", false)] - public void IsAppInForegroundTest(string packageName, bool expected) - { - string[] requests = - [ + $"exec:cmd package 'install-write' -S {dpiStream.Length} 936013062 splitAPK1.apk", "host:transport:169.254.109.177:5555", - "shell:dumpsys activity activities | grep mResumedActivity" + "exec:cmd package 'install-commit' 936013062" ]; - byte[] streamData = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} - mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"u8.ToArray(); - using MemoryStream shellStream = new(streamData); - - bool result = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.IsAppInForeground(Device, packageName)); - - Assert.Equal(expected, result); - } - - /// - /// Tests the method. - /// - [Theory] - [InlineData("com.google.android.gms", "21216 27761\r\n", AppStatus.Background)] - [InlineData("com.android.gallery3d", "\r\n", AppStatus.Stopped)] - public void GetAppStatusTest(string packageName, string response, AppStatus expected) - { - string[] requests = + byte[][] responses = [ - "host:transport:169.254.109.177:5555", - "shell:dumpsys activity activities | grep mResumedActivity", - "host:transport:169.254.109.177:5555", - $"shell:pidof {packageName}" + Encoding.ASCII.GetBytes($"Success: streamed {baseStream.Length} bytes\n"), + Encoding.ASCII.GetBytes($"Success: streamed {abiStream.Length} bytes\n"), + Encoding.ASCII.GetBytes($"Success: streamed {dpiStream.Length} bytes\n") ]; - byte[] activityData = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} - mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"u8.ToArray(); - using MemoryStream activityStream = new(activityData); - byte[] pidData = Encoding.UTF8.GetBytes(response); - using MemoryStream pidStream = new(pidData); + using MemoryStream sessionStream = new(Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n")); + using MemoryStream commitStream = new("Success\n"u8.ToArray()); - AppStatus result = RunTest( - OkResponses(4), + RunTest( + OkResponses(10), NoResponseMessages, requests, - [activityStream, pidStream], - () => TestClient.GetAppStatus(Device, packageName)); - - Assert.Equal(expected, result); + NoSyncRequests, + NoSyncResponses, + responses, + applicationDataChunks, + [sessionStream, commitStream], + () => TestClient.InstallMultiple(Device, baseStream, [abiStream, dpiStream], + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.CreateSession, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); } /// - /// Tests the method. + /// Tests the method. /// - [Theory] - [InlineData("app.lawnchair", AppStatus.Foreground)] - [InlineData("com.android.settings", AppStatus.Foreground)] - public void GetAppStatusForegroundTest(string packageName, AppStatus expected) + [Fact] + public void InstallCreateTest() { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:dumpsys activity activities | grep mResumedActivity" + "exec:cmd package 'install-create' -p com.google.android.gms" ]; - byte[] streamData = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} - mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"u8.ToArray(); + byte[] streamData = Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n"); using MemoryStream shellStream = new(streamData); - AppStatus result = RunTest( + string session = RunTest( OkResponses(2), NoResponseMessages, requests, [shellStream], - () => TestClient.GetAppStatus(Device, packageName)); + () => TestClient.InstallCreate(Device, "com.google.android.gms")); - Assert.Equal(expected, result); + Assert.Equal("936013062", session); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public void FindElementTest() + public void InstallWriteTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - using MemoryStream shellStream = new(streamData); + using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; - Element element = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.FindElement(Device)); - - Assert.Equal(144, element.GetChildCount()); - Element child = element[0][0][0][0][0][0][0][0][2][1][0][0]; - Assert.Equal("where-where", child.Text); - Assert.Equal("android.widget.TextView", child.Class); - Assert.Equal("com.bilibili.app.in", child.Package); - Assert.Equal("com.bilibili.app.in:id/header_info_name", child.ResourceID); - Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), child.Bounds); - Assert.Equal(child, element.FindDescendantOrSelf(x => x.Text == "where-where")); - Assert.Equal(2, element.FindDescendants().Where(x => x.Text == "where-where").Count()); - } + while ((read = stream.Read(buffer.AsSpan(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } - /// - /// Tests the method. - /// - [Fact] - public void FindElementsTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:uiautomator dump /dev/tty" - ]; + using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + string[] requests = + [ + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {stream.Length} 936013062 base.apk" + ]; - string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); - byte[] streamData = Encoding.UTF8.GetBytes(dump); - using MemoryStream shellStream = new(streamData); + byte[] response = Encoding.ASCII.GetBytes($"Success: streamed {stream.Length} bytes\n"); - Element[] elements = RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.FindElements(Device).ToArray()); - - int childCount = elements.Length; - Array.ForEach(elements, x => childCount += x.GetChildCount()); - Assert.Equal(145, childCount); - Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; - Assert.Equal("where-where", element.Text); - Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), element.Bounds); + RunTest( + OkResponses(2), + NoResponseMessages, + requests, + NoSyncRequests, + NoSyncResponses, + [response], + applicationDataChunks, + () => TestClient.InstallWrite(Device, stream, "base", "936013062", new InstallProgress())); + } } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public void SendKeyEventTest() + public void InstallCommitTest() { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_MOVE_END" + "exec:cmd package 'install-commit' 936013062" ]; - using MemoryStream shellStream = new(); + byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); + using MemoryStream shellStream = new(streamData); RunTest( OkResponses(2), NoResponseMessages, requests, [shellStream], - () => TestClient.SendKeyEvent(Device, "KEYCODE_MOVE_END")); + () => TestClient.InstallCommit(Device, "936013062")); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public void SendTextTest() + public void UninstallTest() { string[] requests = [ "host:transport:169.254.109.177:5555", - "shell:input text Hello, World", + "exec:cmd package 'uninstall' com.android.gallery3d" ]; - using MemoryStream shellStream = new(); + byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); + using MemoryStream shellStream = new(streamData); RunTest( OkResponses(2), NoResponseMessages, requests, [shellStream], - () => TestClient.SendText(Device, "Hello, World")); - } - - /// - /// Tests the method. - /// - [Fact] - public void ClearInputTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_MOVE_END", - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL" - ]; - - using MemoryStream firstShellStream = new(); - using MemoryStream secondShellStream = new(); - - RunTest( - OkResponses(4), - NoResponseMessages, - requests, - [firstShellStream, secondShellStream], - () => TestClient.ClearInput(Device, 3)); - } - - /// - /// Tests the method. - /// - [Fact] - public void StartAppTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:monkey -p com.android.settings 1", - ]; - - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - () => TestClient.StartApp(Device, "com.android.settings")); - } - - /// - /// Tests the method. - /// - [Fact] - public void StopAppTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:am force-stop com.android.settings", - ]; - - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - () => TestClient.StopApp(Device, "com.android.settings")); + () => TestClient.Uninstall(Device, "com.android.gallery3d")); } /// - /// Tests the method. + /// Tests the method. /// [Fact] - public void ClickBackButtonTest() + public void GetFeatureSetTest() { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_BACK" - ]; - - using MemoryStream shellStream = new(); + string[] requests = ["host-serial:169.254.109.177:5555:features"]; + string[] responses = ["sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2\r\n"]; - RunTest( - OkResponses(2), - NoResponseMessages, + string[] features = RunTest( + OkResponse, + responses, requests, - [shellStream], - () => TestClient.ClickBackButton(Device)); - } - - /// - /// Tests the method. - /// - [Fact] - public void ClickHomeButtonTest() - { - string[] requests = - [ - "host:transport:169.254.109.177:5555", - "shell:input keyevent KEYCODE_HOME" - ]; - - using MemoryStream shellStream = new(); + () => TestClient.GetFeatureSet(Device).ToArray()); - RunTest( - OkResponses(2), - NoResponseMessages, - requests, - [shellStream], - () => TestClient.ClickHomeButton(Device)); + Assert.Equal(12, features.Length); + Assert.Equal("sendrecv_v2_brotli", features.FirstOrDefault()); + Assert.Equal("stat_v2", features.LastOrDefault()); } private void RunConnectTest(Action test, string connectString) @@ -1625,5 +1144,59 @@ private void RunCreateForwardTest(Action test, string forwardString) requests, () => test(Device)); } + + private struct InstallProgress(params PackageInstallProgressState[] states) : IProgress, IProgress + { + private PackageInstallProgressState? state; + private int packageFinished; + private int packageRequired; + private double uploadProgress; + + private int step = 0; + + public void Report(InstallProgressEventArgs value) + { + if (value.State == state) + { + Assert.True(uploadProgress <= value.UploadProgress, $"{nameof(value.UploadProgress)}: {value.UploadProgress} is less than {uploadProgress}."); + Assert.True(packageFinished <= value.PackageFinished, $"{nameof(value.PackageFinished)}: {value.PackageFinished} is less than {packageFinished}."); + } + else + { + Assert.Equal(states[step++], value.State); + } + + if (value.State is + PackageInstallProgressState.CreateSession + or PackageInstallProgressState.Installing + or PackageInstallProgressState.Finished) + { + Assert.Equal(0, value.UploadProgress); + Assert.Equal(0, value.PackageRequired); + Assert.Equal(0, value.PackageFinished); + } + else + { + if (packageRequired == 0) + { + packageRequired = value.PackageRequired; + } + else + { + Assert.Equal(packageRequired, value.PackageRequired); + } + } + + state = value.State; + packageFinished = value.PackageFinished; + uploadProgress = value.UploadProgress; + } + + public void Report(double value) + { + Assert.True(uploadProgress <= value, $"{nameof(value)}: {value} is less than {uploadProgress}."); + uploadProgress = value; + } + } } } diff --git a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj index e052c0a5..112bf97e 100644 --- a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj +++ b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj @@ -29,8 +29,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_clean.txt b/AdvancedSharpAdbClient.Tests/Assets/DumpScreen.Clean.xml similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/dumpscreen_clean.txt rename to AdvancedSharpAdbClient.Tests/Assets/DumpScreen.Clean.xml diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_error.txt b/AdvancedSharpAdbClient.Tests/Assets/DumpScreen.Error.txt similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/dumpscreen_error.txt rename to AdvancedSharpAdbClient.Tests/Assets/DumpScreen.Error.txt diff --git a/AdvancedSharpAdbClient.Tests/Assets/DumpScreen.MIUI.txt b/AdvancedSharpAdbClient.Tests/Assets/DumpScreen.MIUI.txt new file mode 100644 index 00000000..03b7f30f --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/Assets/DumpScreen.MIUI.txt @@ -0,0 +1,31 @@ +java.io.FileNotFoundException: /data/system/theme_config/theme_compatibility.xml: open failed: ENOENT (No such file or directory) + at libcore.io.IoBridge.open(IoBridge.java:574) + at java.io.FileInputStream.(FileInputStream.java:160) + at java.io.FileInputStream.(FileInputStream.java:115) + at java.io.FileReader.(FileReader.java:60) + at miui.content.res.ThemeCompatibilityLoader.getVersion(ThemeCompatibilityLoader.java:108) + at miui.content.res.ThemeCompatibilityLoader.getConfigDocumentTree(ThemeCompatibilityLoader.java:126) + at miui.content.res.ThemeCompatibilityLoader.loadConfig(ThemeCompatibilityLoader.java:59) + at miui.content.res.ThemeCompatibility.(ThemeCompatibility.java:31) + at miui.content.res.ThemeCompatibility.isThemeEnabled(ThemeCompatibility.java:111) + at android.content.res.MiuiResourcesImpl.(MiuiResourcesImpl.java:41) + at android.content.res.MiuiResources.(MiuiResources.java:58) + at android.content.res.IMiuiResourceImpl.createResources(IMiuiResourceImpl.java:13) + at android.content.res.ThemeManagerStub.createMiuiResources(ThemeManagerStub.java:56) + at android.content.res.Resources.getSystem(Resources.java:235) + at android.util.MiuiMultiWindowAdapter.(MiuiMultiWindowAdapter.java:81) + at android.util.MiuiMultiWindowAdapter.getSize(MiuiMultiWindowAdapter.java:305) + at com.xiaomi.freeform.MiuiFreeformImpl.getSize(MiuiFreeformImpl.java:53) + at android.util.MiuiFreeformUtils.getSize(MiuiFreeformUtils.java:49) + at android.view.Display.getSize(Display.java:796) + at com.android.commands.uiautomator.DumpCommand.run(DumpCommand.java:110) + at com.android.commands.uiautomator.Launcher.main(Launcher.java:83) + at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) + at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:363) +Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory) + at libcore.io.Linux.open(Native Method) + at libcore.io.ForwardingOs.open(ForwardingOs.java:563) + at libcore.io.BlockGuardOs.open(BlockGuardOs.java:274) + at libcore.io.IoBridge.open(IoBridge.java:560) + ... 22 more +UI hierchary dumped to: /dev/tty \ No newline at end of file diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen.txt b/AdvancedSharpAdbClient.Tests/Assets/DumpScreen.txt similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/dumpscreen.txt rename to AdvancedSharpAdbClient.Tests/Assets/DumpScreen.txt diff --git a/AdvancedSharpAdbClient.Tests/Assets/gapps.txt b/AdvancedSharpAdbClient.Tests/Assets/DumpSys.GApps.txt similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/gapps.txt rename to AdvancedSharpAdbClient.Tests/Assets/DumpSys.GApps.txt diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpsys_package.txt b/AdvancedSharpAdbClient.Tests/Assets/DumpSys.Package.txt similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/dumpsys_package.txt rename to AdvancedSharpAdbClient.Tests/Assets/DumpSys.Package.txt diff --git a/AdvancedSharpAdbClient.Tests/Assets/framebuffer.bin b/AdvancedSharpAdbClient.Tests/Assets/Framebuffer.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/framebuffer.bin rename to AdvancedSharpAdbClient.Tests/Assets/Framebuffer.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/framebufferheader-empty.bin b/AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.Empty.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/framebufferheader-empty.bin rename to AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.Empty.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/framebufferheader-v1.bin b/AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.V1.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/framebufferheader-v1.bin rename to AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.V1.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/framebufferheader-v2.bin b/AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.V2.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/framebufferheader-v2.bin rename to AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.V2.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/framebufferheader.bin b/AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/framebufferheader.bin rename to AdvancedSharpAdbClient.Tests/Assets/FramebufferHeader.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/fstab.bin b/AdvancedSharpAdbClient.Tests/Assets/Fstab.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/fstab.bin rename to AdvancedSharpAdbClient.Tests/Assets/Fstab.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/logcat.bin b/AdvancedSharpAdbClient.Tests/Assets/Logcat.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/logcat.bin rename to AdvancedSharpAdbClient.Tests/Assets/Logcat.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/logcatevents.bin b/AdvancedSharpAdbClient.Tests/Assets/LogcatEvents.bin similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/logcatevents.bin rename to AdvancedSharpAdbClient.Tests/Assets/LogcatEvents.bin diff --git a/AdvancedSharpAdbClient.Tests/Assets/printenv.txt b/AdvancedSharpAdbClient.Tests/Assets/PrintEnv.txt similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/printenv.txt rename to AdvancedSharpAdbClient.Tests/Assets/PrintEnv.txt diff --git a/AdvancedSharpAdbClient.Tests/Assets/testapp.apk b/AdvancedSharpAdbClient.Tests/Assets/TestApp/base.apk similarity index 100% rename from AdvancedSharpAdbClient.Tests/Assets/testapp.apk rename to AdvancedSharpAdbClient.Tests/Assets/TestApp/base.apk diff --git a/AdvancedSharpAdbClient.Tests/Assets/TestApp/split_config.arm64_v8a.apk b/AdvancedSharpAdbClient.Tests/Assets/TestApp/split_config.arm64_v8a.apk new file mode 100644 index 00000000..35ef9084 Binary files /dev/null and b/AdvancedSharpAdbClient.Tests/Assets/TestApp/split_config.arm64_v8a.apk differ diff --git a/AdvancedSharpAdbClient.Tests/Assets/TestApp/split_config.xxhdpi.apk b/AdvancedSharpAdbClient.Tests/Assets/TestApp/split_config.xxhdpi.apk new file mode 100644 index 00000000..9dfb4402 Binary files /dev/null and b/AdvancedSharpAdbClient.Tests/Assets/TestApp/split_config.xxhdpi.apk differ diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_miui.txt b/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_miui.txt deleted file mode 100644 index 0f34f37a..00000000 --- a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_miui.txt +++ /dev/null @@ -1,31 +0,0 @@ -java.io.FileNotFoundException: /data/system/theme_config/theme_compatibility.xml: open failed: ENOENT (No such file or directory) - at libcore.io.IoBridge.open(IoBridge.java:574) - at java.io.FileInputStream.(FileInputStream.java:160) - at java.io.FileInputStream.(FileInputStream.java:115) - at java.io.FileReader.(FileReader.java:60) - at miui.content.res.ThemeCompatibilityLoader.getVersion(ThemeCompatibilityLoader.java:108) - at miui.content.res.ThemeCompatibilityLoader.getConfigDocumentTree(ThemeCompatibilityLoader.java:126) - at miui.content.res.ThemeCompatibilityLoader.loadConfig(ThemeCompatibilityLoader.java:59) - at miui.content.res.ThemeCompatibility.(ThemeCompatibility.java:31) - at miui.content.res.ThemeCompatibility.isThemeEnabled(ThemeCompatibility.java:111) - at android.content.res.MiuiResourcesImpl.(MiuiResourcesImpl.java:41) - at android.content.res.MiuiResources.(MiuiResources.java:58) - at android.content.res.IMiuiResourceImpl.createResources(IMiuiResourceImpl.java:13) - at android.content.res.ThemeManagerStub.createMiuiResources(ThemeManagerStub.java:56) - at android.content.res.Resources.getSystem(Resources.java:235) - at android.util.MiuiMultiWindowAdapter.(MiuiMultiWindowAdapter.java:81) - at android.util.MiuiMultiWindowAdapter.getSize(MiuiMultiWindowAdapter.java:305) - at com.xiaomi.freeform.MiuiFreeformImpl.getSize(MiuiFreeformImpl.java:53) - at android.util.MiuiFreeformUtils.getSize(MiuiFreeformUtils.java:49) - at android.view.Display.getSize(Display.java:796) - at com.android.commands.uiautomator.DumpCommand.run(DumpCommand.java:110) - at com.android.commands.uiautomator.Launcher.main(Launcher.java:83) - at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) - at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:363) -Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory) - at libcore.io.Linux.open(Native Method) - at libcore.io.ForwardingOs.open(ForwardingOs.java:563) - at libcore.io.BlockGuardOs.open(BlockGuardOs.java:274) - at libcore.io.IoBridge.open(IoBridge.java:560) - ... 22 more -UI hierchary dumped to: /dev/tty \ No newline at end of file diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_miui_clean.txt b/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_miui_clean.txt deleted file mode 100644 index 2c583156..00000000 --- a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen_miui_clean.txt +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/AdvancedSharpAdbClient.Tests/Assets/test.txt b/AdvancedSharpAdbClient.Tests/Assets/test.txt deleted file mode 100644 index 99b60400..00000000 --- a/AdvancedSharpAdbClient.Tests/Assets/test.txt +++ /dev/null @@ -1 +0,0 @@ -This is a test file. \ No newline at end of file diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs new file mode 100644 index 00000000..4678eb89 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.Async.cs @@ -0,0 +1,469 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using System.Xml; +using Xunit; + +namespace AdvancedSharpAdbClient.DeviceCommands.Tests +{ + public partial class DeviceExtensionsTests + { + /// + /// Tests the method. + /// + [Fact] + public async void DumpScreenStringAsyncTest() + { + string dump = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt"); + string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = dump; + + string xml = await new DeviceClient(client, Device).DumpScreenStringAsync(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.Equal(cleanDump, xml); + } + + /// + /// Tests the method. + /// + [Fact] + public async void DumpScreenStringMIUIAsyncTest() + { + string miuidump = await File.ReadAllTextAsync(@"Assets/DumpScreen.MIUI.txt"); + string cleanMIUIDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = miuidump; + + string miuiXml = await new DeviceClient(client, Device).DumpScreenStringAsync(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.Equal(cleanMIUIDump, miuiXml); + } + + /// + /// Tests the method. + /// + [Fact] + public async void DumpScreenStringEmptyAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = string.Empty; + + string emptyXml = await new DeviceClient(client, Device).DumpScreenStringAsync(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.True(string.IsNullOrEmpty(emptyXml)); + } + + /// + /// Tests the method. + /// + [Fact] + public async void DumpScreenStringErrorAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.Error.txt"); + + _ = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).DumpScreenStringAsync()); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void DumpScreenAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt"); + + XmlDocument xml = await new DeviceClient(client, Device).DumpScreenAsync(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + XmlDocument doc = new(); + doc.LoadXml(cleanDump); + + Assert.Equal(doc, xml); + } + +#if WINDOWS10_0_17763_0_OR_GREATER + /// + /// Tests the method. + /// + [Fact] + public async void DumpScreenWinRTAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = await File.ReadAllTextAsync(@"Assets/DumpScreen.txt"); + + Windows.Data.Xml.Dom.XmlDocument xml = await new DeviceClient(client, Device).DumpScreenWinRTAsync(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + string cleanDump = await File.ReadAllTextAsync(@"Assets/DumpScreen.Clean.xml"); + Windows.Data.Xml.Dom.XmlDocument doc = new(); + doc.LoadXml(cleanDump); + + Assert.Equal(doc.InnerText, xml.InnerText); + } + +#endif + + /// + /// Tests the method. + /// + [Fact] + public async void ClickAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input tap 100 100"] = @"java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission + at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) + at android.os.Parcel.createException(Parcel.java:2357) + at android.os.Parcel.readException(Parcel.java:2340) + at android.os.Parcel.readException(Parcel.java:2282) + at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) + at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) + at com.android.commands.input.Input.injectMotionEvent(Input.java:397) + at com.android.commands.input.Input.access$200(Input.java:41) + at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) + at com.android.commands.input.Input$InputTap.run(Input.java:217) + at com.android.commands.input.Input.onRun(Input.java:107) + at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) + at com.android.commands.input.Input.main(Input.java:71) + at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) + at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) +Caused by: android.os.RemoteException: Remote stack trace: + at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) + at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) + at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) + at android.os.Binder.execTransactInternal(Binder.java:1165) + at android.os.Binder.execTransact(Binder.java:1134)"; + + JavaException exception = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).ClickAsync(100, 100)); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input tap 100 100", client.ReceivedCommands[0]); + + Assert.Equal("SecurityException", exception.JavaName); + Assert.Equal("Injecting to another application requires INJECT_EVENTS permission", exception.Message); + Assert.Equal(@" at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) + at android.os.Parcel.createException(Parcel.java:2357) + at android.os.Parcel.readException(Parcel.java:2340) + at android.os.Parcel.readException(Parcel.java:2282) + at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) + at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) + at com.android.commands.input.Input.injectMotionEvent(Input.java:397) + at com.android.commands.input.Input.access$200(Input.java:41) + at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) + at com.android.commands.input.Input$InputTap.run(Input.java:217) + at com.android.commands.input.Input.onRun(Input.java:107) + at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) + at com.android.commands.input.Input.main(Input.java:71) + at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) + at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) +Caused by: android.os.RemoteException: Remote stack trace: + at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) + at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) + at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) + at android.os.Binder.execTransactInternal(Binder.java:1165) + at android.os.Binder.execTransact(Binder.java:1134)", exception.JavaStackTrace, ignoreLineEndingDifferences: true); + } + + /// + /// Tests the method. + /// + [Fact] + public async void ClickCordsAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input tap 100 100"] = "Error: Injecting to another application requires INJECT_EVENTS permission\r\n"; + + _ = await Assert.ThrowsAsync(() => new DeviceClient(client, Device).ClickAsync(new Point(100, 100))); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input tap 100 100", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void SwipeAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; + + await new DeviceClient(client, Device).SwipeAsync(100, 200, 300, 400, 500); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void SwipePointAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; + + await new DeviceClient(client, Device).SwipeAsync(new Point(100, 200), new Point(300, 400), 500); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void SwipeElementAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; + + await new DeviceClient(client, Device).SwipeAsync(new Element(client, Device, new Rectangle(0, 0, 200, 400)), new Element(client, Device, new Rectangle(0, 0, 600, 800)), 500); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("21216 27761\r\n", true)] + [InlineData(" 21216 27761\r\n", true)] + [InlineData("12836\r\n", true)] + [InlineData(" \r\n", false)] + [InlineData("\r\n", false)] + [InlineData(" ", false)] + [InlineData("", false)] + public async void IsAppRunningAsyncTest(string response, bool expected) + { + DummyAdbClient client = new(); + client.Commands["shell:pidof com.google.android.gms"] = response; + + bool result = await new DeviceClient(client, Device).IsAppRunningAsync("com.google.android.gms"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:pidof com.google.android.gms", client.ReceivedCommands[0]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("app.lawnchair", true)] + [InlineData("com.android.settings", true)] + [InlineData("com.google.android.gms", false)] + public async void IsAppInForegroundAsyncTest(string packageName, bool expected) + { + DummyAdbClient client = new(); + client.Commands["shell:dumpsys activity activities | grep mResumedActivity"] = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} + mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"; + + bool result = await new DeviceClient(client, Device).IsAppInForegroundAsync(packageName); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("com.google.android.gms", "21216 27761\r\n", AppStatus.Background)] + [InlineData("com.android.gallery3d", "\r\n", AppStatus.Stopped)] + public async void GetAppStatusAsyncTest(string packageName, string response, AppStatus expected) + { + DummyAdbClient client = new(); + client.Commands["shell:dumpsys activity activities | grep mResumedActivity"] = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} + mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"; + client.Commands[$"shell:pidof {packageName}"] = response; + + AppStatus result = await new DeviceClient(client, Device).GetAppStatusAsync(packageName); + + Assert.Equal(2, client.ReceivedCommands.Count); + Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); + Assert.Equal($"shell:pidof {packageName}", client.ReceivedCommands[1]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("app.lawnchair", AppStatus.Foreground)] + [InlineData("com.android.settings", AppStatus.Foreground)] + public async void GetAppStatusForegroundAsyncTest(string packageName, AppStatus expected) + { + DummyAdbClient client = new(); + client.Commands["shell:dumpsys activity activities | grep mResumedActivity"] = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} + mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"; + + AppStatus result = await new DeviceClient(client, Device).GetAppStatusAsync(packageName); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Fact] + public async void FindElementAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + Element element = await new DeviceClient(client, Device).FindElementAsync(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.Equal(144, element.GetChildCount()); + Element child = element[0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", child.Text); + Assert.Equal("android.widget.TextView", child.Class); + Assert.Equal("com.bilibili.app.in", child.Package); + Assert.Equal("com.bilibili.app.in:id/header_info_name", child.ResourceID); + Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), child.Bounds); + Assert.Equal(child, element.FindDescendantOrSelf(x => x.Text == "where-where")); + Assert.Equal(2, element.FindDescendants().Where(x => x.Text == "where-where").Count()); + } + + /// + /// Tests the method. + /// + [Fact] + public async void FindElementsAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + Element[] elements = (await new DeviceClient(client, Device).FindElementsAsync()).ToArray(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + int childCount = elements.Length; + Array.ForEach(elements, x => childCount += x.GetChildCount()); + Assert.Equal(145, childCount); + Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", element.Text); + Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), element.Bounds); + } + + /// + /// Tests the method. + /// + [Fact] + public async void FindAsyncElementsTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + List elements = []; + await foreach (Element value in new DeviceClient(client, Device).FindAsyncElements()) + { + elements.Add(value); + } + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + int childCount = elements.Count; + elements.ForEach(x => childCount += x.GetChildCount()); + Assert.Equal(145, childCount); + Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", element.Text); + Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), element.Bounds); + } + + /// + /// Tests the method. + /// + [Fact] + public async void SendKeyEventAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_MOVE_END"] = string.Empty; + + await new DeviceClient(client, Device).SendKeyEventAsync("KEYCODE_MOVE_END"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input keyevent KEYCODE_MOVE_END", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void SendTextAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input text Hello, World"] = string.Empty; + + await new DeviceClient(client, Device).SendTextAsync("Hello, World"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input text Hello, World", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void StartAppAsyncTest() + { + DummyAdbClient client = new(); + + await new DeviceClient(client, Device).StartAppAsync("com.android.settings"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:monkey -p com.android.settings 1", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void StopAppAsyncTest() + { + DummyAdbClient client = new(); + + await new DeviceClient(client, Device).StopAppAsync("com.android.settings"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:am force-stop com.android.settings", client.ReceivedCommands[0]); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.cs new file mode 100644 index 00000000..6e175e65 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceClientTexts.cs @@ -0,0 +1,457 @@ +using NSubstitute; +using System; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Xml; +using Xunit; + +namespace AdvancedSharpAdbClient.DeviceCommands.Tests +{ + public partial class DeviceClientTexts + { + protected static DeviceData Device { get; } = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + [Fact] + public void ConstructorNullTest() + { + _ = Assert.Throws(() => new DeviceClient(null, null)); + _ = Assert.Throws(() => new DeviceClient(null, new DeviceData())); + _ = Assert.Throws(() => new DeviceClient(Substitute.For(), null)); + _ = Assert.Throws(() => new DeviceClient(Substitute.For(), new DeviceData())); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenStringTest() + { + string dump = File.ReadAllText(@"Assets/DumpScreen.txt"); + string cleanDump = File.ReadAllText(@"Assets/DumpScreen.Clean.xml"); + + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = dump; + + string xml = new DeviceClient(client, Device).DumpScreenString(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.Equal(cleanDump, xml); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenStringMIUITest() + { + string miuidump = File.ReadAllText(@"Assets/DumpScreen.MIUI.txt"); + string cleanMIUIDump = File.ReadAllText(@"Assets/DumpScreen.Clean.xml"); + + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = miuidump; + + string miuiXml = new DeviceClient(client, Device).DumpScreenString(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.Equal(cleanMIUIDump, miuiXml); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenStringEmptyTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = string.Empty; + + string emptyXml = new DeviceClient(client, Device).DumpScreenString(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.True(string.IsNullOrEmpty(emptyXml)); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenStringErrorTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.Error.txt"); + + _ = Assert.Throws(() => new DeviceClient(client, Device).DumpScreenString()); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + XmlDocument xml = new DeviceClient(client, Device).DumpScreen(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + string cleanDump = File.ReadAllText(@"Assets/DumpScreen.Clean.xml"); + XmlDocument doc = new(); + doc.LoadXml(cleanDump); + + Assert.Equal(doc, xml); + } + +#if WINDOWS10_0_17763_0_OR_GREATER + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenWinRTTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + Windows.Data.Xml.Dom.XmlDocument xml = new DeviceClient(client, Device).DumpScreenWinRT(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + string cleanDump = File.ReadAllText(@"Assets/DumpScreen.Clean.xml"); + Windows.Data.Xml.Dom.XmlDocument doc = new(); + doc.LoadXml(cleanDump); + + Assert.Equal(doc.InnerText, xml.InnerText); + } + +#endif + + /// + /// Tests the method. + /// + [Fact] + public void ClickTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input tap 100 100"] = @"java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission + at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) + at android.os.Parcel.createException(Parcel.java:2357) + at android.os.Parcel.readException(Parcel.java:2340) + at android.os.Parcel.readException(Parcel.java:2282) + at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) + at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) + at com.android.commands.input.Input.injectMotionEvent(Input.java:397) + at com.android.commands.input.Input.access$200(Input.java:41) + at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) + at com.android.commands.input.Input$InputTap.run(Input.java:217) + at com.android.commands.input.Input.onRun(Input.java:107) + at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) + at com.android.commands.input.Input.main(Input.java:71) + at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) + at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) +Caused by: android.os.RemoteException: Remote stack trace: + at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) + at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) + at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) + at android.os.Binder.execTransactInternal(Binder.java:1165) + at android.os.Binder.execTransact(Binder.java:1134)"; + + JavaException exception = Assert.Throws(() => new DeviceClient(client, Device).Click(100, 100)); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input tap 100 100", client.ReceivedCommands[0]); + + Assert.Equal("SecurityException", exception.JavaName); + Assert.Equal("Injecting to another application requires INJECT_EVENTS permission", exception.Message); + Assert.Equal(@" at android.os.Parcel.createExceptionOrNull(Parcel.java:2373) + at android.os.Parcel.createException(Parcel.java:2357) + at android.os.Parcel.readException(Parcel.java:2340) + at android.os.Parcel.readException(Parcel.java:2282) + at android.hardware.input.IInputManager$Stub$Proxy.injectInputEvent(IInputManager.java:946) + at android.hardware.input.InputManager.injectInputEvent(InputManager.java:907) + at com.android.commands.input.Input.injectMotionEvent(Input.java:397) + at com.android.commands.input.Input.access$200(Input.java:41) + at com.android.commands.input.Input$InputTap.sendTap(Input.java:223) + at com.android.commands.input.Input$InputTap.run(Input.java:217) + at com.android.commands.input.Input.onRun(Input.java:107) + at com.android.internal.os.BaseCommand.run(BaseCommand.java:60) + at com.android.commands.input.Input.main(Input.java:71) + at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method) + at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:438) +Caused by: android.os.RemoteException: Remote stack trace: + at com.android.server.input.InputManagerService.injectInputEventInternal(InputManagerService.java:677) + at com.android.server.input.InputManagerService.injectInputEvent(InputManagerService.java:651) + at android.hardware.input.IInputManager$Stub.onTransact(IInputManager.java:430) + at android.os.Binder.execTransactInternal(Binder.java:1165) + at android.os.Binder.execTransact(Binder.java:1134)", exception.JavaStackTrace, ignoreLineEndingDifferences: true); + } + + /// + /// Tests the method. + /// + [Fact] + public void ClickCordsTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input tap 100 100"] = "Error: Injecting to another application requires INJECT_EVENTS permission\r\n"; + + _ = Assert.Throws(() => new DeviceClient(client, Device).Click(new Point(100, 100))); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input tap 100 100", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void SwipeTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; + + new DeviceClient(client, Device).Swipe(100, 200, 300, 400, 500); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void SwipePointTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; + + new DeviceClient(client, Device).Swipe(new Point(100, 200), new Point(300, 400), 500); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void SwipeElementTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input swipe 100 200 300 400 500"] = string.Empty; + + new DeviceClient(client, Device).Swipe(new Element(client, Device, new Rectangle(0, 0, 200, 400)), new Element(client, Device, new Rectangle(0, 0, 600, 800)), 500); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input swipe 100 200 300 400 500", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("21216 27761\r\n", true)] + [InlineData(" 21216 27761\r\n", true)] + [InlineData("12836\r\n", true)] + [InlineData(" \r\n", false)] + [InlineData("\r\n", false)] + [InlineData(" ", false)] + [InlineData("", false)] + public void IsAppRunningTest(string response, bool expected) + { + DummyAdbClient client = new(); + client.Commands["shell:pidof com.google.android.gms"] = response; + + bool result = new DeviceClient(client, Device).IsAppRunning("com.google.android.gms"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:pidof com.google.android.gms", client.ReceivedCommands[0]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("app.lawnchair", true)] + [InlineData("com.android.settings", true)] + [InlineData("com.google.android.gms", false)] + public void IsAppInForegroundTest(string packageName, bool expected) + { + DummyAdbClient client = new(); + client.Commands["shell:dumpsys activity activities | grep mResumedActivity"] = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} + mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"; + + bool result = new DeviceClient(client, Device).IsAppInForeground(packageName); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("com.google.android.gms", "21216 27761\r\n", AppStatus.Background)] + [InlineData("com.android.gallery3d", "\r\n", AppStatus.Stopped)] + public void GetAppStatusTest(string packageName, string response, AppStatus expected) + { + DummyAdbClient client = new(); + client.Commands["shell:dumpsys activity activities | grep mResumedActivity"] = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} + mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"; + client.Commands[$"shell:pidof {packageName}"] = response; + + AppStatus result = new DeviceClient(client, Device).GetAppStatus(packageName); + + Assert.Equal(2, client.ReceivedCommands.Count); + Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); + Assert.Equal($"shell:pidof {packageName}", client.ReceivedCommands[1]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Theory] + [InlineData("app.lawnchair", AppStatus.Foreground)] + [InlineData("com.android.settings", AppStatus.Foreground)] + public void GetAppStatusForegroundTest(string packageName, AppStatus expected) + { + DummyAdbClient client = new(); + client.Commands["shell:dumpsys activity activities | grep mResumedActivity"] = @" mResumedActivity: ActivityRecord{1f5309a u0 com.android.settings/.homepage.SettingsHomepageActivity t61029} + mResumedActivity: ActivityRecord{896cc3 u0 app.lawnchair/.LawnchairLauncher t5}"; + + AppStatus result = new DeviceClient(client, Device).GetAppStatus(packageName); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:dumpsys activity activities | grep mResumedActivity", client.ReceivedCommands[0]); + + Assert.Equal(expected, result); + } + + /// + /// Tests the method. + /// + [Fact] + public void FindElementTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + Element element = new DeviceClient(client, Device).FindElement(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + Assert.Equal(144, element.GetChildCount()); + Element child = element[0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", child.Text); + Assert.Equal("android.widget.TextView", child.Class); + Assert.Equal("com.bilibili.app.in", child.Package); + Assert.Equal("com.bilibili.app.in:id/header_info_name", child.ResourceID); + Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), child.Bounds); + Assert.Equal(child, element.FindDescendantOrSelf(x => x.Text == "where-where")); + Assert.Equal(2, element.FindDescendants().Where(x => x.Text == "where-where").Count()); + } + + /// + /// Tests the method. + /// + [Fact] + public void FindElementsTest() + { + DummyAdbClient client = new(); + client.Commands["shell:uiautomator dump /dev/tty"] = File.ReadAllText(@"Assets/DumpScreen.txt"); + + Element[] elements = new DeviceClient(client, Device).FindElements().ToArray(); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:uiautomator dump /dev/tty", client.ReceivedCommands[0]); + + int childCount = elements.Length; + Array.ForEach(elements, x => childCount += x.GetChildCount()); + Assert.Equal(145, childCount); + Element element = elements[0][0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", element.Text); + Assert.Equal(Rectangle.FromLTRB(45, 889, 427, 973), element.Bounds); + } + + /// + /// Tests the method. + /// + [Fact] + public void SendKeyEventTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_MOVE_END"] = string.Empty; + + new DeviceClient(client, Device).SendKeyEvent("KEYCODE_MOVE_END"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input keyevent KEYCODE_MOVE_END", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void SendTextTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input text Hello, World"] = string.Empty; + + new DeviceClient(client, Device).SendText("Hello, World"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input text Hello, World", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void StartAppTest() + { + DummyAdbClient client = new(); + + new DeviceClient(client, Device).StartApp("com.android.settings"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:monkey -p com.android.settings 1", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void StopAppTest() + { + DummyAdbClient client = new(); + + new DeviceClient(client, Device).StopApp("com.android.settings"); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:am force-stop com.android.settings", client.ReceivedCommands[0]); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs index e07e8b09..b9a75121 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs @@ -8,6 +8,53 @@ namespace AdvancedSharpAdbClient.DeviceCommands.Tests { public partial class DeviceExtensionsTests { + /// + /// Tests the method. + /// + [Fact] + public async void ClearInputAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_MOVE_END"] = string.Empty; + client.Commands["shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL"] = string.Empty; + + await client.ClearInputAsync(Device, 3); + + Assert.Equal(2, client.ReceivedCommands.Count); + Assert.Equal("shell:input keyevent KEYCODE_MOVE_END", client.ReceivedCommands[0]); + Assert.Equal("shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL", client.ReceivedCommands[1]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void ClickBackButtonAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_BACK"] = string.Empty; + + await client.ClickBackButtonAsync(Device); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input keyevent KEYCODE_BACK", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public async void ClickHomeButtonAsyncTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_HOME"] = string.Empty; + + await client.ClickHomeButtonAsync(Device); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input keyevent KEYCODE_HOME", client.ReceivedCommands[0]); + } + [Fact] public async void StatAsyncTest() { @@ -51,7 +98,6 @@ public async void UninstallPackageAsyncTests() { DummyAdbClient adbClient = new(); - adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; adbClient.Commands["shell:pm uninstall com.example"] = "Success"; DeviceData device = new() @@ -60,9 +106,8 @@ public async void UninstallPackageAsyncTests() }; await adbClient.UninstallPackageAsync(device, "com.example"); - Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal("shell:pm uninstall com.example", adbClient.ReceivedCommands[1]); + Assert.Single(adbClient.ReceivedCommands); + Assert.Equal("shell:pm uninstall com.example", adbClient.ReceivedCommands[0]); } [Theory] @@ -296,7 +341,6 @@ public async void GetPackageVersionAsyncTest(string command, int versionCode, st { DummyAdbClient adbClient = new(); - adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; adbClient.Commands[$"shell:dumpsys package {packageName}"] = command; DeviceData device = new() @@ -308,9 +352,8 @@ public async void GetPackageVersionAsyncTest(string command, int versionCode, st Assert.Equal(versionCode, version.VersionCode); Assert.Equal(versionName, version.VersionName); - Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal($"shell:dumpsys package {packageName}", adbClient.ReceivedCommands[1]); + Assert.Single(adbClient.ReceivedCommands); + Assert.Equal($"shell:dumpsys package {packageName}", adbClient.ReceivedCommands[0]); } [Fact] diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs index d96a0bcf..e3e5f268 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs @@ -10,6 +10,59 @@ namespace AdvancedSharpAdbClient.DeviceCommands.Tests /// public partial class DeviceExtensionsTests { + protected static DeviceData Device { get; } = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + /// + /// Tests the method. + /// + [Fact] + public void ClearInputTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_MOVE_END"] = string.Empty; + client.Commands["shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL"] = string.Empty; + + client.ClearInput(Device, 3); + + Assert.Equal(2, client.ReceivedCommands.Count); + Assert.Equal("shell:input keyevent KEYCODE_MOVE_END", client.ReceivedCommands[0]); + Assert.Equal("shell:input keyevent KEYCODE_DEL KEYCODE_DEL KEYCODE_DEL", client.ReceivedCommands[1]); + } + + /// + /// Tests the method. + /// + [Fact] + public void ClickBackButtonTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_BACK"] = string.Empty; + + client.ClickBackButton(Device); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input keyevent KEYCODE_BACK", client.ReceivedCommands[0]); + } + + /// + /// Tests the method. + /// + [Fact] + public void ClickHomeButtonTest() + { + DummyAdbClient client = new(); + client.Commands["shell:input keyevent KEYCODE_HOME"] = string.Empty; + + client.ClickHomeButton(Device); + + Assert.Single(client.ReceivedCommands); + Assert.Equal("shell:input keyevent KEYCODE_HOME", client.ReceivedCommands[0]); + } + [Fact] public void StatTest() { @@ -19,15 +72,13 @@ public void StatTest() ISyncService mock = Substitute.For(); mock.Stat("/test").Returns(stats); - DeviceData device = new(); - Factories.SyncServiceFactory = (c, d) => { Factories.Reset(); return mock; }; - Assert.Equal(stats, client.Stat(device, "/test")); + Assert.Equal(stats, client.Stat(Device, "/test")); } [Fact] @@ -37,9 +88,7 @@ public void GetEnvironmentVariablesTest() adbClient.Commands[$"shell:{EnvironmentVariablesReceiver.PrintEnvCommand}"] = "a=b"; - DeviceData device = new(); - - Dictionary variables = adbClient.GetEnvironmentVariables(device); + Dictionary variables = adbClient.GetEnvironmentVariables(Device); Assert.NotNull(variables); Assert.Single(variables.Keys); Assert.True(variables.ContainsKey("a")); @@ -51,18 +100,12 @@ public void UninstallPackageTests() { DummyAdbClient adbClient = new(); - adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; adbClient.Commands["shell:pm uninstall com.example"] = "Success"; - DeviceData device = new() - { - State = DeviceState.Online - }; - adbClient.UninstallPackage(device, "com.example"); + adbClient.UninstallPackage(Device, "com.example"); - Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal("shell:pm uninstall com.example", adbClient.ReceivedCommands[1]); + Assert.Single(adbClient.ReceivedCommands); + Assert.Equal("shell:pm uninstall com.example", adbClient.ReceivedCommands[0]); } [Theory] @@ -296,21 +339,15 @@ public void GetPackageVersionTest(string command, int versionCode, string versio { DummyAdbClient adbClient = new(); - adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; adbClient.Commands[$"shell:dumpsys package {packageName}"] = command; - DeviceData device = new() - { - State = DeviceState.Online - }; - VersionInfo version = adbClient.GetPackageVersion(device, packageName); + VersionInfo version = adbClient.GetPackageVersion(Device, packageName); Assert.Equal(versionCode, version.VersionCode); Assert.Equal(versionName, version.VersionName); - Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal($"shell:dumpsys package {packageName}", adbClient.ReceivedCommands[1]); + Assert.Single(adbClient.ReceivedCommands); + Assert.Equal($"shell:dumpsys package {packageName}", adbClient.ReceivedCommands[0]); } [Fact] @@ -342,8 +379,7 @@ public void ListProcessesTest() 3 (ksoftirqd/0) S 2 0 0 0 -1 69238848 0 0 0 0 0 23 0 0 20 0 1 0 7 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579284070 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; - DeviceData device = new(); - AndroidProcess[] processes = adbClient.ListProcesses(device).ToArray(); + AndroidProcess[] processes = adbClient.ListProcesses(Device).ToArray(); Assert.Equal(3, processes.Length); Assert.Equal("init", processes[0].Name); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs index 77723e5e..af667cb2 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs @@ -58,8 +58,8 @@ public void CombineTest() [Fact] public void CombineCurrentDirTest() { - string result = LinuxPath.Combine(".", "test.txt"); - Assert.Equal("./test.txt", result); + string result = LinuxPath.Combine(".", "Test.txt"); + Assert.Equal("./Test.txt", result); } [Fact] diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs index 99b8e753..17cb9016 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs @@ -7,7 +7,7 @@ using System; using Xunit; -namespace AdvancedSharpAdbClient.Models.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Models.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs index 3d93c4fa..34b2e050 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs @@ -1,6 +1,6 @@ using Xunit; -namespace AdvancedSharpAdbClient.Models.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Models.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs index 2d62a816..bb57f0de 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs @@ -11,8 +11,8 @@ public async void InstallRemotePackageAsyncTest() DummyAdbClient adbClient = new(); adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; - adbClient.Commands["shell:pm install \"/data/test.apk\""] = "Success"; - adbClient.Commands["shell:pm install -r -t \"/data/test.apk\""] = "Success"; + adbClient.Commands["shell:pm install \"/data/base.apk\""] = "Success"; + adbClient.Commands["shell:pm install -r -t \"/data/base.apk\""] = "Success"; DeviceData device = new() { @@ -21,17 +21,17 @@ public async void InstallRemotePackageAsyncTest() PackageManager manager = new(adbClient, device); - await manager.InstallRemotePackageAsync("/data/test.apk", new InstallProgress(PackageInstallProgressState.Installing)); + await manager.InstallRemotePackageAsync("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing)); Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm install \"/data/test.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:pm install \"/data/base.apk\"", adbClient.ReceivedCommands[1]); adbClient.ReceivedCommands.Clear(); - await manager.InstallRemotePackageAsync("/data/test.apk", new InstallProgress(PackageInstallProgressState.Installing), default, "-r", "-t"); + await manager.InstallRemotePackageAsync("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing), default, "-r", "-t"); Assert.Single(adbClient.ReceivedCommands); - Assert.Equal("shell:pm install -r -t \"/data/test.apk\"", adbClient.ReceivedCommands[0]); + Assert.Equal("shell:pm install -r -t \"/data/base.apk\"", adbClient.ReceivedCommands[0]); } [Fact] @@ -42,8 +42,8 @@ public async void InstallPackageAsyncTest() DummyAdbClient adbClient = new(); adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; - adbClient.Commands["shell:pm install \"/data/local/tmp/test.txt\""] = "Success"; - adbClient.Commands["shell:rm \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["shell:pm install \"/data/local/tmp/base.apk\""] = "Success"; + adbClient.Commands["shell:rm \"/data/local/tmp/base.apk\""] = string.Empty; DeviceData device = new() { @@ -52,21 +52,20 @@ public async void InstallPackageAsyncTest() PackageManager manager = new(adbClient, device, (c, d) => syncService); - await manager.InstallPackageAsync("Assets/test.txt", + await manager.InstallPackageAsync("Assets/TestApp/base.apk", new InstallProgress( + PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, PackageInstallProgressState.PostInstall, PackageInstallProgressState.Finished)); Assert.Equal(3, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm install \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:rm \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:rm \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[2]); Assert.Single(syncService.UploadedFiles); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/test.txt")); - - Factories.Reset(); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/base.apk")); } [Fact] @@ -80,8 +79,8 @@ public async void InstallMultipleRemotePackageAsyncTest() adbClient.Commands["shell:pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; adbClient.Commands["shell:pm install-create -p com.google.android.gms -r -t"] = "Success: created install session [936013062]"; adbClient.Commands["shell:pm install-write 936013062 base.apk \"/data/base.apk\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\""] = "Success"; adbClient.Commands["shell:pm install-commit 936013062"] = "Success"; DeviceData device = new() @@ -91,7 +90,7 @@ public async void InstallMultipleRemotePackageAsyncTest() PackageManager manager = new(adbClient, device); - await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split-dpi.apk", "/data/split-abi.apk"], + await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -100,13 +99,13 @@ await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split- Assert.Equal(6, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create", adbClient.ReceivedCommands[1]); Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/base.apk\"", adbClient.ReceivedCommands[2]); - Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[3..5]); - Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[3..5]); + Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[3..5]); + Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[3..5]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[5]); adbClient.ReceivedCommands.Clear(); - await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split-dpi.apk", "/data/split-abi.apk"], + await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -115,13 +114,13 @@ await manager.InstallMultipleRemotePackageAsync("/data/base.apk", ["/data/split- Assert.Equal(5, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -r -t", adbClient.ReceivedCommands[0]); Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/base.apk\"", adbClient.ReceivedCommands[1]); - Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[2..4]); - Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[2..4]); + Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[2..4]); + Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[2..4]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[4]); adbClient.ReceivedCommands.Clear(); - await manager.InstallMultipleRemotePackageAsync(["/data/split-dpi.apk", "/data/split-abi.apk"], "com.google.android.gms", + await manager.InstallMultipleRemotePackageAsync(["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], "com.google.android.gms", new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -129,13 +128,13 @@ await manager.InstallMultipleRemotePackageAsync(["/data/split-dpi.apk", "/data/s Assert.Equal(4, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[0]); - Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[1..3]); - Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[1..3]); + Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[1..3]); + Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[1..3]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[3]); adbClient.ReceivedCommands.Clear(); - await manager.InstallMultipleRemotePackageAsync(["/data/split-dpi.apk", "/data/split-abi.apk"], "com.google.android.gms", + await manager.InstallMultipleRemotePackageAsync(["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], "com.google.android.gms", new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -143,8 +142,8 @@ await manager.InstallMultipleRemotePackageAsync(["/data/split-dpi.apk", "/data/s Assert.Equal(4, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms -r -t", adbClient.ReceivedCommands[0]); - Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[1..3]); - Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[1..3]); + Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[1..3]); + Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[1..3]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[3]); } @@ -158,13 +157,13 @@ public async void InstallMultiplePackageAsyncTest() adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; adbClient.Commands["shell:pm install-create"] = "Success: created install session [936013062]"; adbClient.Commands["shell:pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; - adbClient.Commands["shell:pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/local/tmp/gapps.txt\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/local/tmp/logcat.bin\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 base.apk \"/data/local/tmp/base.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/local/tmp/split_config.arm64_v8a.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/local/tmp/split_config.xxhdpi.apk\""] = "Success"; adbClient.Commands["shell:pm install-commit 936013062"] = "Success"; - adbClient.Commands["shell:rm \"/data/local/tmp/test.txt\""] = string.Empty; - adbClient.Commands["shell:rm \"/data/local/tmp/gapps.txt\""] = string.Empty; - adbClient.Commands["shell:rm \"/data/local/tmp/logcat.bin\""] = string.Empty; + adbClient.Commands["shell:rm \"/data/local/tmp/base.apk\""] = string.Empty; + adbClient.Commands["shell:rm \"/data/local/tmp/split_config.arm64_v8a.apk\""] = string.Empty; + adbClient.Commands["shell:rm \"/data/local/tmp/split_config.xxhdpi.apk\""] = string.Empty; DeviceData device = new() { @@ -173,8 +172,9 @@ public async void InstallMultiplePackageAsyncTest() PackageManager manager = new(adbClient, device, (c, d) => syncService); - await manager.InstallMultiplePackageAsync("Assets/test.txt", ["Assets/gapps.txt", "Assets/logcat.bin"], + await manager.InstallMultiplePackageAsync("Assets/TestApp/base.apk", ["Assets/TestApp/split_config.arm64_v8a.apk", "Assets/TestApp/split_config.xxhdpi.apk"], new InstallProgress( + PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -184,24 +184,25 @@ await manager.InstallMultiplePackageAsync("Assets/test.txt", ["Assets/gapps.txt" Assert.Equal(9, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[2]); - Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[3..5]); - Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[3..5]); + Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[2]); + Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[3..5]); + Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[3..5]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[5]); - Assert.Contains("shell:rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[6..8]); - Assert.Contains("shell:rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[6..8]); - Assert.Equal("shell:rm \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[8]); + Assert.Contains("shell:rm \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[6..8]); + Assert.Contains("shell:rm \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[6..8]); + Assert.Equal("shell:rm \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[8]); Assert.Equal(3, syncService.UploadedFiles.Count); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/test.txt")); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/gapps.txt")); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/logcat.bin")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/base.apk")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.arm64_v8a.apk")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.xxhdpi.apk")); syncService.UploadedFiles.Clear(); adbClient.ReceivedCommands.Clear(); - await manager.InstallMultiplePackageAsync(["Assets/gapps.txt", "Assets/logcat.bin"], "com.google.android.gms", + await manager.InstallMultiplePackageAsync(["Assets/TestApp/split_config.arm64_v8a.apk", "Assets/TestApp/split_config.xxhdpi.apk"], "com.google.android.gms", new InstallProgress( + PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -211,15 +212,15 @@ await manager.InstallMultiplePackageAsync(["Assets/gapps.txt", "Assets/logcat.bi Assert.Equal(6, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[0]); - Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[1..3]); - Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[1..3]); + Assert.Contains("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[1..3]); + Assert.Contains("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[1..3]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[3]); - Assert.Contains("shell:rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[4..6]); - Assert.Contains("shell:rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[4..6]); + Assert.Contains("shell:rm \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[4..6]); + Assert.Contains("shell:rm \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[4..6]); Assert.Equal(2, syncService.UploadedFiles.Count); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/gapps.txt")); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/logcat.bin")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.arm64_v8a.apk")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.xxhdpi.apk")); } [Fact] @@ -249,7 +250,7 @@ public async void GetPackageVersionInfoAsyncTest() }; DummyAdbClient client = new(); - client.Commands["shell:dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/gapps.txt"); + client.Commands["shell:dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/DumpSys.GApps.txt"); PackageManager manager = new(client, device, skipInit: true); VersionInfo versionInfo = await manager.GetVersionInfoAsync("com.google.android.gms"); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs index c0a86332..4181559a 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs @@ -39,8 +39,8 @@ public void InstallRemotePackageTest() DummyAdbClient adbClient = new(); adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; - adbClient.Commands["shell:pm install \"/data/test.apk\""] = "Success"; - adbClient.Commands["shell:pm install -r -t \"/data/test.apk\""] = "Success"; + adbClient.Commands["shell:pm install \"/data/base.apk\""] = "Success"; + adbClient.Commands["shell:pm install -r -t \"/data/base.apk\""] = "Success"; DeviceData device = new() { @@ -49,17 +49,17 @@ public void InstallRemotePackageTest() PackageManager manager = new(adbClient, device); - manager.InstallRemotePackage("/data/test.apk", new InstallProgress(PackageInstallProgressState.Installing)); + manager.InstallRemotePackage("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing)); Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm install \"/data/test.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:pm install \"/data/base.apk\"", adbClient.ReceivedCommands[1]); adbClient.ReceivedCommands.Clear(); - manager.InstallRemotePackage("/data/test.apk", new InstallProgress(PackageInstallProgressState.Installing), "-r", "-t"); + manager.InstallRemotePackage("/data/base.apk", new InstallProgress(PackageInstallProgressState.Installing), "-r", "-t"); Assert.Single(adbClient.ReceivedCommands); - Assert.Equal("shell:pm install -r -t \"/data/test.apk\"", adbClient.ReceivedCommands[0]); + Assert.Equal("shell:pm install -r -t \"/data/base.apk\"", adbClient.ReceivedCommands[0]); } [Fact] @@ -70,8 +70,8 @@ public void InstallPackageTest() DummyAdbClient adbClient = new(); adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; - adbClient.Commands["shell:pm install \"/data/local/tmp/test.txt\""] = "Success"; - adbClient.Commands["shell:rm \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["shell:pm install \"/data/local/tmp/base.apk\""] = "Success"; + adbClient.Commands["shell:rm \"/data/local/tmp/base.apk\""] = string.Empty; DeviceData device = new() { @@ -80,19 +80,20 @@ public void InstallPackageTest() PackageManager manager = new(adbClient, device, (c, d) => syncService); - manager.InstallPackage("Assets/test.txt", + manager.InstallPackage("Assets/TestApp/base.apk", new InstallProgress( + PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.Installing, PackageInstallProgressState.PostInstall, PackageInstallProgressState.Finished)); Assert.Equal(3, adbClient.ReceivedCommands.Count); - Assert.Equal("shell:pm install \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:rm \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:rm \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[2]); Assert.Single(syncService.UploadedFiles); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/test.txt")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/base.apk")); } [Fact] @@ -106,8 +107,8 @@ public void InstallMultipleRemotePackageTest() adbClient.Commands["shell:pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; adbClient.Commands["shell:pm install-create -p com.google.android.gms -r -t"] = "Success: created install session [936013062]"; adbClient.Commands["shell:pm install-write 936013062 base.apk \"/data/base.apk\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\""] = "Success"; adbClient.Commands["shell:pm install-commit 936013062"] = "Success"; DeviceData device = new() @@ -117,7 +118,7 @@ public void InstallMultipleRemotePackageTest() PackageManager manager = new(adbClient, device); - manager.InstallMultipleRemotePackage("/data/base.apk", ["/data/split-dpi.apk", "/data/split-abi.apk"], + manager.InstallMultipleRemotePackage("/data/base.apk", ["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -126,13 +127,13 @@ public void InstallMultipleRemotePackageTest() Assert.Equal(6, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create", adbClient.ReceivedCommands[1]); Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/base.apk\"", adbClient.ReceivedCommands[2]); - Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[3]); - Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[4]); + Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[3]); + Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[4]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[5]); adbClient.ReceivedCommands.Clear(); - manager.InstallMultipleRemotePackage("/data/base.apk", ["/data/split-dpi.apk", "/data/split-abi.apk"], + manager.InstallMultipleRemotePackage("/data/base.apk", ["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -141,13 +142,13 @@ public void InstallMultipleRemotePackageTest() Assert.Equal(5, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -r -t", adbClient.ReceivedCommands[0]); Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/base.apk\"", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[2]); - Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[3]); + Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[3]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[4]); adbClient.ReceivedCommands.Clear(); - manager.InstallMultipleRemotePackage(["/data/split-dpi.apk", "/data/split-abi.apk"], "com.google.android.gms", + manager.InstallMultipleRemotePackage(["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], "com.google.android.gms", new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -155,13 +156,13 @@ public void InstallMultipleRemotePackageTest() Assert.Equal(4, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[0]); - Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[2]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[3]); adbClient.ReceivedCommands.Clear(); - manager.InstallMultipleRemotePackage(["/data/split-dpi.apk", "/data/split-abi.apk"], "com.google.android.gms", + manager.InstallMultipleRemotePackage(["/data/split_config.arm64_v8a.apk", "/data/split_config.xxhdpi.apk"], "com.google.android.gms", new InstallProgress( PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -169,8 +170,8 @@ public void InstallMultipleRemotePackageTest() Assert.Equal(4, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms -r -t", adbClient.ReceivedCommands[0]); - Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[2]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[3]); } @@ -184,13 +185,13 @@ public void InstallMultiplePackageTest() adbClient.Commands["shell:pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; adbClient.Commands["shell:pm install-create"] = "Success: created install session [936013062]"; adbClient.Commands["shell:pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; - adbClient.Commands["shell:pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/local/tmp/gapps.txt\""] = "Success"; - adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/local/tmp/logcat.bin\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 base.apk \"/data/local/tmp/base.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split0.apk \"/data/local/tmp/split_config.arm64_v8a.apk\""] = "Success"; + adbClient.Commands["shell:pm install-write 936013062 split1.apk \"/data/local/tmp/split_config.xxhdpi.apk\""] = "Success"; adbClient.Commands["shell:pm install-commit 936013062"] = "Success"; - adbClient.Commands["shell:rm \"/data/local/tmp/test.txt\""] = string.Empty; - adbClient.Commands["shell:rm \"/data/local/tmp/gapps.txt\""] = string.Empty; - adbClient.Commands["shell:rm \"/data/local/tmp/logcat.bin\""] = string.Empty; + adbClient.Commands["shell:rm \"/data/local/tmp/base.apk\""] = string.Empty; + adbClient.Commands["shell:rm \"/data/local/tmp/split_config.arm64_v8a.apk\""] = string.Empty; + adbClient.Commands["shell:rm \"/data/local/tmp/split_config.xxhdpi.apk\""] = string.Empty; DeviceData device = new() { @@ -199,8 +200,9 @@ public void InstallMultiplePackageTest() PackageManager manager = new(adbClient, device, (c, d) => syncService); - manager.InstallMultiplePackage("Assets/test.txt", ["Assets/gapps.txt", "Assets/logcat.bin"], + manager.InstallMultiplePackage("Assets/TestApp/base.apk", ["Assets/TestApp/split_config.arm64_v8a.apk", "Assets/TestApp/split_config.xxhdpi.apk"], new InstallProgress( + PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -210,24 +212,25 @@ public void InstallMultiplePackageTest() Assert.Equal(9, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[2]); - Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[3]); - Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[4]); + Assert.Equal("shell:pm install-write 936013062 base.apk \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[3]); + Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[4]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[5]); - Assert.Equal("shell:rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[6]); - Assert.Equal("shell:rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[7]); - Assert.Equal("shell:rm \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[8]); + Assert.Equal("shell:rm \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[6]); + Assert.Equal("shell:rm \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[7]); + Assert.Equal("shell:rm \"/data/local/tmp/base.apk\"", adbClient.ReceivedCommands[8]); Assert.Equal(3, syncService.UploadedFiles.Count); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/test.txt")); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/gapps.txt")); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/logcat.bin")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/base.apk")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.arm64_v8a.apk")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.xxhdpi.apk")); syncService.UploadedFiles.Clear(); adbClient.ReceivedCommands.Clear(); - manager.InstallMultiplePackage(["Assets/gapps.txt", "Assets/logcat.bin"], "com.google.android.gms", + manager.InstallMultiplePackage(["Assets/TestApp/split_config.arm64_v8a.apk", "Assets/TestApp/split_config.xxhdpi.apk"], "com.google.android.gms", new InstallProgress( + PackageInstallProgressState.Preparing, PackageInstallProgressState.Uploading, PackageInstallProgressState.CreateSession, PackageInstallProgressState.WriteSession, @@ -237,15 +240,15 @@ public void InstallMultiplePackageTest() Assert.Equal(6, adbClient.ReceivedCommands.Count); Assert.Equal("shell:pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[0]); - Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[1]); - Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[2]); + Assert.Equal("shell:pm install-write 936013062 split0.apk \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[1]); + Assert.Equal("shell:pm install-write 936013062 split1.apk \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[2]); Assert.Equal("shell:pm install-commit 936013062", adbClient.ReceivedCommands[3]); - Assert.Equal("shell:rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[4]); - Assert.Equal("shell:rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[5]); + Assert.Equal("shell:rm \"/data/local/tmp/split_config.arm64_v8a.apk\"", adbClient.ReceivedCommands[4]); + Assert.Equal("shell:rm \"/data/local/tmp/split_config.xxhdpi.apk\"", adbClient.ReceivedCommands[5]); Assert.Equal(2, syncService.UploadedFiles.Count); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/gapps.txt")); - Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/logcat.bin")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.arm64_v8a.apk")); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/split_config.xxhdpi.apk")); } [Fact] @@ -275,7 +278,7 @@ public void GetPackageVersionInfoTest() }; DummyAdbClient client = new(); - client.Commands["shell:dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/gapps.txt"); + client.Commands["shell:dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/DumpSys.GApps.txt"); PackageManager manager = new(client, device, skipInit: true); VersionInfo versionInfo = manager.GetVersionInfo("com.google.android.gms"); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs index 88bd4b29..77e931ad 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs @@ -1,6 +1,6 @@ using Xunit; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs index da7f71b9..3798d827 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Xunit; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers.Tests { public class GetPropReceiverTests { diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs index a0871234..47bfcc54 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs @@ -1,6 +1,6 @@ using Xunit; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers.Tests { public class InstallOutputReceiverTests { diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs index 1e2086fa..122b3ff0 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs @@ -1,6 +1,6 @@ using Xunit; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers.Tests { public class PackageManagerReceiverTests { diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs index 80fb796d..e1b03ca9 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs @@ -1,6 +1,6 @@ using Xunit; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs index 879856d1..5ca72ca5 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs @@ -8,7 +8,7 @@ using System.IO; using Xunit; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers.Tests { /// /// Tests the class. @@ -37,7 +37,7 @@ public void GetVersionTest() Assert.Null(receiver.GetVersionName(" versionName")); Assert.Equal(string.Empty, receiver.GetVersionName(" versionName=")); - string dumpsys = string.Join(Environment.NewLine, File.ReadAllLines(@"Assets/dumpsys_package.txt")); + string dumpsys = string.Join(Environment.NewLine, File.ReadAllLines(@"Assets/DumpSys.Package.txt")); receiver = new VersionInfoReceiver(); StringReader reader = new(dumpsys); diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs index bd6717d8..148bc6c3 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Drawing; using System.IO; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Xml; namespace AdvancedSharpAdbClient.Tests { @@ -139,14 +137,6 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket #region Not Implemented - void IAdbClient.Click(DeviceData device, Point cords) => throw new NotImplementedException(); - - void IAdbClient.Click(DeviceData device, int x, int y) => throw new NotImplementedException(); - - Task IAdbClient.ClickAsync(DeviceData device, Point cords, CancellationToken cancellationToken) => throw new NotImplementedException(); - - Task IAdbClient.ClickAsync(DeviceData device, int x, int y, CancellationToken cancellationToken) => throw new NotImplementedException(); - string IAdbClient.Connect(DnsEndPoint endpoint) => throw new NotImplementedException(); Task IAdbClient.ConnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken) => throw new NotImplementedException(); @@ -165,38 +155,10 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.DisconnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken) => throw new NotImplementedException(); - XmlDocument IAdbClient.DumpScreen(DeviceData device) => throw new NotImplementedException(); - - Task IAdbClient.DumpScreenAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); - - string IAdbClient.DumpScreenString(DeviceData device) => throw new NotImplementedException(); - - Task IAdbClient.DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); - -#if WINDOWS10_0_17763_0_OR_GREATER - Windows.Data.Xml.Dom.XmlDocument IAdbClient.DumpScreenWinRT(DeviceData device) => throw new NotImplementedException(); - - Task IAdbClient.DumpScreenWinRTAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); -#endif - - IAsyncEnumerable IAdbClient.FindAsyncElements(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); - - Element IAdbClient.FindElement(DeviceData device, string xpath, TimeSpan timeout) => throw new NotImplementedException(); - - Task IAdbClient.FindElementAsync(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); - - IEnumerable IAdbClient.FindElements(DeviceData device, string xpath, TimeSpan timeout) => throw new NotImplementedException(); - - Task> IAdbClient.FindElementsAsync(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); - int IAdbClient.GetAdbVersion() => throw new NotImplementedException(); Task IAdbClient.GetAdbVersionAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); - AppStatus IAdbClient.GetAppStatus(DeviceData device, string packageName) => throw new NotImplementedException(); - - Task IAdbClient.GetAppStatusAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); - IEnumerable IAdbClient.GetDevices() => throw new NotImplementedException(); Task> IAdbClient.GetDevicesAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); @@ -209,9 +171,9 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.GetFrameBufferAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); - void IAdbClient.Install(DeviceData device, Stream apk, params string[] arguments) => throw new NotImplementedException(); + void IAdbClient.Install(DeviceData device, Stream apk, IProgress progress, params string[] arguments) => throw new NotImplementedException(); - Task IAdbClient.InstallAsync(DeviceData device, Stream apk, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); + Task IAdbClient.InstallAsync(DeviceData device, Stream apk, IProgress progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); void IAdbClient.InstallCommit(DeviceData device, string session) => throw new NotImplementedException(); @@ -221,25 +183,17 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.InstallCreateAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); - void IAdbClient.InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, params string[] arguments) => throw new NotImplementedException(); + void IAdbClient.InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress progress, params string[] arguments) => throw new NotImplementedException(); - void IAdbClient.InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, params string[] arguments) => throw new NotImplementedException(); + void IAdbClient.InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress progress, params string[] arguments) => throw new NotImplementedException(); - Task IAdbClient.InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); + Task IAdbClient.InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); - Task IAdbClient.InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); + Task IAdbClient.InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); - void IAdbClient.InstallWrite(DeviceData device, Stream apk, string apkName, string session) => throw new NotImplementedException(); + void IAdbClient.InstallWrite(DeviceData device, Stream apk, string apkName, string session, IProgress progress) => throw new NotImplementedException(); - Task IAdbClient.InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, CancellationToken cancellationToken) => throw new NotImplementedException(); - - bool IAdbClient.IsAppInForeground(DeviceData device, string packageName) => throw new NotImplementedException(); - - Task IAdbClient.IsAppInForegroundAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); - - bool IAdbClient.IsAppRunning(DeviceData device, string packageName) => throw new NotImplementedException(); - - Task IAdbClient.IsAppRunningAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); + Task IAdbClient.InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, IProgress progress, CancellationToken cancellationToken) => throw new NotImplementedException(); void IAdbClient.KillAdb() => throw new NotImplementedException(); @@ -285,30 +239,6 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) => throw new NotImplementedException(); - void IAdbClient.SendKeyEvent(DeviceData device, string key) => throw new NotImplementedException(); - - Task IAdbClient.SendKeyEventAsync(DeviceData device, string key, CancellationToken cancellationToken) => throw new NotImplementedException(); - - void IAdbClient.SendText(DeviceData device, string text) => throw new NotImplementedException(); - - Task IAdbClient.SendTextAsync(DeviceData device, string text, CancellationToken cancellationToken) => throw new NotImplementedException(); - - void IAdbClient.StartApp(DeviceData device, string packageName) => throw new NotImplementedException(); - - Task IAdbClient.StartAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); - - void IAdbClient.StopApp(DeviceData device, string packageName) => throw new NotImplementedException(); - - Task IAdbClient.StopAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); - - void IAdbClient.Swipe(DeviceData device, Element first, Element second, long speed) => throw new NotImplementedException(); - - void IAdbClient.Swipe(DeviceData device, int x1, int y1, int x2, int y2, long speed) => throw new NotImplementedException(); - - Task IAdbClient.SwipeAsync(DeviceData device, Element first, Element second, long speed, CancellationToken cancellationToken) => throw new NotImplementedException(); - - Task IAdbClient.SwipeAsync(DeviceData device, int x1, int y1, int x2, int y2, long speed, CancellationToken cancellationToken) => throw new NotImplementedException(); - void IAdbClient.Uninstall(DeviceData device, string packageName, params string[] arguments) => throw new NotImplementedException(); Task IAdbClient.UninstallAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs index 7987d578..40ec2451 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs @@ -22,6 +22,8 @@ public DummyAdbCommandLineClient() : base(ServerName) // No validation done in the dummy adb client. public override bool CheckFileExists(string adbPath) => true; + public override Task CheckFileExistsAsync(string adbPath, CancellationToken cancellationToken = default) => Task.FromResult(true); + protected override int RunProcess(string filename, string command, ICollection errorOutput, ICollection standardOutput) { if (filename == AdbPath) diff --git a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs index d75c7f19..72ab9c3c 100644 --- a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs +++ b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs @@ -10,7 +10,7 @@ public class LogTests [Fact] public void ReadLogTest() { - using FileStream stream = File.OpenRead(@"Assets/logcat.bin"); + using FileStream stream = File.OpenRead(@"Assets/Logcat.bin"); using ShellStream shellStream = new(stream, false); LogReader reader = new(shellStream); @@ -39,7 +39,7 @@ public void ReadLogTest() [Fact] public async void ReadLogAsyncTest() { - await using FileStream stream = File.OpenRead(@"Assets/logcat.bin"); + await using FileStream stream = File.OpenRead(@"Assets/Logcat.bin"); await using ShellStream shellStream = new(stream, false); LogReader reader = new(shellStream); @@ -70,7 +70,7 @@ public void ReadEventLogTest() { // The data in this stream was read using a ShellStream, so the CRLF fixing // has already taken place. - using FileStream stream = File.OpenRead(@"Assets/logcatevents.bin"); + using FileStream stream = File.OpenRead(@"Assets/LogcatEvents.bin"); LogReader reader = new(stream); LogEntry entry = reader.ReadEntry(); @@ -104,7 +104,7 @@ public async void ReadEventLogAsyncTest() { // The data in this stream was read using a ShellStream, so the CRLF fixing // has already taken place. - await using FileStream stream = File.OpenRead(@"Assets/logcatevents.bin"); + await using FileStream stream = File.OpenRead(@"Assets/LogcatEvents.bin"); LogReader reader = new(stream); LogEntry entry = await reader.ReadEntryAsync(); diff --git a/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs b/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs index e1440868..21507cc7 100644 --- a/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs @@ -15,7 +15,7 @@ public class FramebufferHeaderTests [Fact] public void ReadFramebufferTest() { - byte[] data = File.ReadAllBytes("Assets/framebufferheader-v1.bin"); + byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.V1.bin"); FramebufferHeader header = [.. data]; @@ -45,7 +45,7 @@ public void ReadFramebufferTest() [Fact] public void ReadFramebufferV2Test() { - byte[] data = File.ReadAllBytes("Assets/framebufferheader-v2.bin"); + byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.V2.bin"); FramebufferHeader header = [.. data]; @@ -77,9 +77,9 @@ public void ReadFramebufferV2Test() [SupportedOSPlatform("windows")] public void ToImageTest() { - byte[] data = File.ReadAllBytes("Assets/framebufferheader.bin"); + byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.bin"); FramebufferHeader header = FramebufferHeader.Read(data); - byte[] framebuffer = File.ReadAllBytes("Assets/framebuffer.bin"); + byte[] framebuffer = File.ReadAllBytes("Assets/Framebuffer.bin"); using Bitmap image = header.ToImage(framebuffer); Assert.NotNull(image); Assert.Equal(PixelFormat.Format32bppArgb, image.PixelFormat); @@ -98,7 +98,7 @@ public void ToImageTest() [SupportedOSPlatform("windows")] public void ToImageEmptyTest() { - byte[] data = File.ReadAllBytes("Assets/framebufferheader-empty.bin"); + byte[] data = File.ReadAllBytes("Assets/FramebufferHeader.Empty.bin"); FramebufferHeader header = FramebufferHeader.Read(data); byte[] framebuffer = []; diff --git a/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs b/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs index be8e0a92..bcf05f0f 100644 --- a/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/FramebufferTests.cs @@ -3,6 +3,7 @@ using System.Drawing.Imaging; using System.IO; using System.Net; +using System.Runtime.Versioning; using Xunit; namespace AdvancedSharpAdbClient.Models.Tests @@ -24,6 +25,9 @@ public void ConstructorNullTest() } [Fact] +#if WINDOWS + [SupportedOSPlatform("windows")] +#endif public void RefreshTest() { DeviceData device = new() @@ -40,8 +44,8 @@ public void RefreshTest() socket.Requests.Add("host:transport:169.254.109.177:5555"); socket.Requests.Add("framebuffer:"); - socket.SyncDataReceived.Enqueue(File.ReadAllBytes("Assets/framebufferheader.bin")); - socket.SyncDataReceived.Enqueue(File.ReadAllBytes("Assets/framebuffer.bin")); + socket.SyncDataReceived.Enqueue(File.ReadAllBytes("Assets/FramebufferHeader.bin")); + socket.SyncDataReceived.Enqueue(File.ReadAllBytes("Assets/Framebuffer.bin")); using Framebuffer framebuffer = new(device, (endPoint) => socket); @@ -101,8 +105,8 @@ public async void RefreshAsyncTest() socket.Requests.Add("host:transport:169.254.109.177:5555"); socket.Requests.Add("framebuffer:"); - socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/framebufferheader.bin")); - socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/framebuffer.bin")); + socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/FramebufferHeader.bin")); + socket.SyncDataReceived.Enqueue(await File.ReadAllBytesAsync("Assets/Framebuffer.bin")); using Framebuffer framebuffer = new(device, (endPoint) => socket); diff --git a/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs b/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs index bf4ebe71..8876232c 100644 --- a/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs +++ b/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs @@ -1,12 +1,11 @@ #region AdvancedSharpAdbClient -global using AdvancedSharpAdbClient.DeviceCommands; +global using AdvancedSharpAdbClient.DeviceCommands.Models; +global using AdvancedSharpAdbClient.DeviceCommands.Receivers; global using AdvancedSharpAdbClient.Exceptions; global using AdvancedSharpAdbClient.Logs; global using AdvancedSharpAdbClient.Models; -global using AdvancedSharpAdbClient.Models.DeviceCommands; global using AdvancedSharpAdbClient.Polyfills; global using AdvancedSharpAdbClient.Receivers; -global using AdvancedSharpAdbClient.Receivers.DeviceCommands; #endregion #region AdvancedSharpAdbClient.Tests diff --git a/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs b/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs index 46da400c..48de73b7 100644 --- a/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs @@ -51,14 +51,14 @@ public void ThrowOnErrorTest() AssertThrowsException("/dev/test: access denied"); // Should not thrown an exception - ConsoleOutputReceiver receiver = new(); - receiver.ThrowOnError("Stay calm and watch cat movies."); + ConsoleOutputReceiver receiver = new() { ParsesErrors = true }; + receiver.AddOutput("Stay calm and watch cat movies."); } private static void AssertThrowsException(string line) where T : Exception { - ConsoleOutputReceiver receiver = new(); - _ = Assert.Throws(() => receiver.ThrowOnError(line)); + ConsoleOutputReceiver receiver = new() { ParsesErrors = true }; + _ = Assert.Throws(() => receiver.AddOutput(line)); } } } diff --git a/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs b/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs index 9afb513b..fa0cb53c 100644 --- a/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs +++ b/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace AdvancedSharpAdbClient +namespace AdvancedSharpAdbClient.Tests { public class SocketBasedTests { @@ -267,11 +267,11 @@ protected void RunTest( Assert.Empty(Socket.ShellStreams); // Make sure a request was sent - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { @@ -280,7 +280,7 @@ protected void RunTest( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -291,23 +291,23 @@ protected void RunTest( { // Make sure the traffic sent on the wire matches the traffic // we have defined in our unit test. - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { Assert.Empty(Socket.SyncRequests); } - Assert.Equal(responses.ToArray(), Socket.Responses); - Assert.Equal(responseMessages.ToArray(), Socket.ResponseMessages); + Assert.Equal(responses, Socket.Responses); + Assert.Equal(responseMessages, Socket.ResponseMessages); if (syncResponses != null) { - Assert.Equal(syncResponses.ToArray(), Socket.SyncResponses); + Assert.Equal(syncResponses, Socket.SyncResponses); } else { @@ -316,7 +316,7 @@ protected void RunTest( if (syncDataReceived != null) { - AssertEqual(syncDataReceived.ToArray(), Socket.SyncDataReceived.ToArray()); + Assert.Equal(syncDataReceived, Socket.SyncDataReceived); } else { @@ -325,7 +325,7 @@ protected void RunTest( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -334,7 +334,7 @@ protected void RunTest( if (shellStreams != null) { - Assert.Equal(shellStreams.ToArray(), [.. Socket.ShellStreams]); + Assert.Equal(shellStreams, Socket.ShellStreams); } else { @@ -560,11 +560,11 @@ protected TResult RunTest( Assert.Empty(Socket.ShellStreams); // Make sure a request was sent - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { @@ -573,7 +573,7 @@ protected TResult RunTest( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -584,23 +584,23 @@ protected TResult RunTest( { // Make sure the traffic sent on the wire matches the traffic // we have defined in our unit test. - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { Assert.Empty(Socket.SyncRequests); } - Assert.Equal(responses.ToArray(), Socket.Responses); - Assert.Equal(responseMessages.ToArray(), Socket.ResponseMessages); + Assert.Equal(responses, Socket.Responses); + Assert.Equal(responseMessages, Socket.ResponseMessages); if (syncResponses != null) { - Assert.Equal(syncResponses.ToArray(), Socket.SyncResponses); + Assert.Equal(syncResponses, Socket.SyncResponses); } else { @@ -609,7 +609,7 @@ protected TResult RunTest( if (syncDataReceived != null) { - AssertEqual(syncDataReceived.ToArray(), Socket.SyncDataReceived.ToArray()); + Assert.Equal(syncDataReceived, Socket.SyncDataReceived); } else { @@ -618,7 +618,7 @@ protected TResult RunTest( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -627,7 +627,7 @@ protected TResult RunTest( if (shellStreams != null) { - Assert.Equal(shellStreams.ToArray(), [.. Socket.ShellStreams]); + Assert.Equal(shellStreams, Socket.ShellStreams); } else { @@ -853,11 +853,11 @@ protected async Task RunTestAsync( Assert.Empty(Socket.ShellStreams); // Make sure a request was sent - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { @@ -866,7 +866,7 @@ protected async Task RunTestAsync( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -877,23 +877,23 @@ protected async Task RunTestAsync( { // Make sure the traffic sent on the wire matches the traffic // we have defined in our unit test. - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { Assert.Empty(Socket.SyncRequests); } - Assert.Equal(responses.ToArray(), Socket.Responses); - Assert.Equal(responseMessages.ToArray(), Socket.ResponseMessages); + Assert.Equal(responses, Socket.Responses); + Assert.Equal(responseMessages, Socket.ResponseMessages); if (syncResponses != null) { - Assert.Equal(syncResponses.ToArray(), Socket.SyncResponses); + Assert.Equal(syncResponses, Socket.SyncResponses); } else { @@ -902,7 +902,7 @@ protected async Task RunTestAsync( if (syncDataReceived != null) { - AssertEqual(syncDataReceived.ToArray(), Socket.SyncDataReceived.ToArray()); + Assert.Equal(syncDataReceived, Socket.SyncDataReceived); } else { @@ -911,7 +911,7 @@ protected async Task RunTestAsync( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -920,7 +920,7 @@ protected async Task RunTestAsync( if (shellStreams != null) { - Assert.Equal(shellStreams.ToArray(), [.. Socket.ShellStreams]); + Assert.Equal(shellStreams, Socket.ShellStreams); } else { @@ -1150,11 +1150,11 @@ protected async Task RunTestAsync( Assert.Empty(Socket.ShellStreams); // Make sure a request was sent - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { @@ -1163,7 +1163,7 @@ protected async Task RunTestAsync( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -1174,23 +1174,23 @@ protected async Task RunTestAsync( { // Make sure the traffic sent on the wire matches the traffic // we have defined in our unit test. - Assert.Equal(requests.ToArray(), Socket.Requests); + Assert.Equal(requests, Socket.Requests); if (syncRequests != null) { - Assert.Equal(syncRequests.ToArray(), Socket.SyncRequests); + Assert.Equal(syncRequests, Socket.SyncRequests); } else { Assert.Empty(Socket.SyncRequests); } - Assert.Equal(responses.ToArray(), Socket.Responses); - Assert.Equal(responseMessages.ToArray(), Socket.ResponseMessages); + Assert.Equal(responses, Socket.Responses); + Assert.Equal(responseMessages, Socket.ResponseMessages); if (syncResponses != null) { - Assert.Equal(syncResponses.ToArray(), Socket.SyncResponses); + Assert.Equal(syncResponses, Socket.SyncResponses); } else { @@ -1199,7 +1199,7 @@ protected async Task RunTestAsync( if (syncDataReceived != null) { - AssertEqual(syncDataReceived.ToArray(), Socket.SyncDataReceived.ToArray()); + Assert.Equal(syncDataReceived, Socket.SyncDataReceived); } else { @@ -1208,7 +1208,7 @@ protected async Task RunTestAsync( if (syncDataSent != null) { - AssertEqual(syncDataSent.ToArray(), Socket.SyncDataSent.ToArray()); + Assert.Equal(syncDataSent, Socket.SyncDataSent); } else { @@ -1217,7 +1217,7 @@ protected async Task RunTestAsync( if (shellStreams != null) { - Assert.Equal(shellStreams.ToArray(), [.. Socket.ShellStreams]); + Assert.Equal(shellStreams, [.. Socket.ShellStreams]); } else { @@ -1231,15 +1231,5 @@ protected async Task RunTestAsync( #endregion protected static IEnumerable OkResponses(int count) => Enumerable.Repeat(AdbResponse.OK, count); - - private static void AssertEqual(IList expected, IList actual) - { - Assert.Equal(expected.Count, actual.Count); - - for (int i = 0; i < expected.Count; i++) - { - Assert.Equal(expected[i], actual[i]); - } - } } } diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index 7aefb0b8..2547944a 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -147,13 +147,13 @@ public async void GetAsyncListingTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void PullAsyncTest() { await using MemoryStream stream = new(); - byte[] content = await File.ReadAllBytesAsync("Assets/fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); byte[] contentLength = BitConverter.GetBytes(content.Length); await RunTestAsync( @@ -182,13 +182,13 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void PushAsyncTest() { - FileStream stream = File.OpenRead("Assets/fstab.bin"); - byte[] content = await File.ReadAllBytesAsync("Assets/fstab.bin"); + FileStream stream = File.OpenRead("Assets/Fstab.bin"); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); byte[] contentMessage = [ .. SyncCommandConverter.GetBytes(SyncCommand.DATA), diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index 7ffeabf3..033048c2 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -98,13 +98,13 @@ public void GetListingTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PullTest() { using MemoryStream stream = new(); - byte[] content = File.ReadAllBytes("Assets/fstab.bin"); + byte[] content = File.ReadAllBytes("Assets/Fstab.bin"); byte[] contentLength = BitConverter.GetBytes(content.Length); RunTest( @@ -133,13 +133,13 @@ public void PullTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PushTest() { - FileStream stream = File.OpenRead("Assets/fstab.bin"); - byte[] content = File.ReadAllBytes("Assets/fstab.bin"); + FileStream stream = File.OpenRead("Assets/Fstab.bin"); + byte[] content = File.ReadAllBytes("Assets/Fstab.bin"); byte[] contentMessage = [ .. SyncCommandConverter.GetBytes(SyncCommand.DATA), diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index c15d9a52..b4ec0753 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -10,18 +10,15 @@ using System.IO; using System.Linq; using System.Net; -using System.Runtime.CompilerServices; using System.Text; -using System.Text.RegularExpressions; using System.Threading; -using System.Xml; namespace AdvancedSharpAdbClient { public partial class AdbClient { /// - public async Task GetAdbVersionAsync(CancellationToken cancellationToken = default) + public virtual async Task GetAdbVersionAsync(CancellationToken cancellationToken = default) { using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -33,7 +30,7 @@ public async Task GetAdbVersionAsync(CancellationToken cancellationToken = } /// - public async Task KillAdbAsync(CancellationToken cancellationToken = default) + public virtual async Task KillAdbAsync(CancellationToken cancellationToken = default) { using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SendAdbRequestAsync("host:kill", cancellationToken).ConfigureAwait(false); @@ -43,7 +40,7 @@ public async Task KillAdbAsync(CancellationToken cancellationToken = default) } /// - public async Task> GetDevicesAsync(CancellationToken cancellationToken = default) + public virtual async Task> GetDevicesAsync(CancellationToken cancellationToken = default) { using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -56,7 +53,7 @@ public async Task> GetDevicesAsync(CancellationToken can } /// - public async Task CreateForwardAsync(DeviceData device, string local, string remote, bool allowRebind, CancellationToken cancellationToken = default) + public virtual async Task CreateForwardAsync(DeviceData device, string local, string remote, bool allowRebind, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -72,7 +69,7 @@ public async Task CreateForwardAsync(DeviceData device, string local, strin } /// - public async Task CreateReverseForwardAsync(DeviceData device, string remote, string local, bool allowRebind, CancellationToken cancellationToken = default) + public virtual async Task CreateReverseForwardAsync(DeviceData device, string remote, string local, bool allowRebind, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -90,7 +87,7 @@ public async Task CreateReverseForwardAsync(DeviceData device, string remot } /// - public async Task RemoveReverseForwardAsync(DeviceData device, string remote, CancellationToken cancellationToken = default) + public virtual async Task RemoveReverseForwardAsync(DeviceData device, string remote, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -102,7 +99,7 @@ public async Task RemoveReverseForwardAsync(DeviceData device, string remote, Ca } /// - public async Task RemoveAllReverseForwardsAsync(DeviceData device, CancellationToken cancellationToken = default) + public virtual async Task RemoveAllReverseForwardsAsync(DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -114,7 +111,7 @@ public async Task RemoveAllReverseForwardsAsync(DeviceData device, CancellationT } /// - public async Task RemoveForwardAsync(DeviceData device, int localPort, CancellationToken cancellationToken = default) + public virtual async Task RemoveForwardAsync(DeviceData device, int localPort, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -124,7 +121,7 @@ public async Task RemoveForwardAsync(DeviceData device, int localPort, Cancellat } /// - public async Task RemoveAllForwardsAsync(DeviceData device, CancellationToken cancellationToken = default) + public virtual async Task RemoveAllForwardsAsync(DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -134,7 +131,7 @@ public async Task RemoveAllForwardsAsync(DeviceData device, CancellationToken ca } /// - public async Task> ListForwardAsync(DeviceData device, CancellationToken cancellationToken = default) + public virtual async Task> ListForwardAsync(DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -148,7 +145,7 @@ public async Task> ListForwardAsync(DeviceData device, } /// - public async Task> ListReverseForwardAsync(DeviceData device, CancellationToken cancellationToken = default) + public virtual async Task> ListReverseForwardAsync(DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -164,7 +161,7 @@ public async Task> ListReverseForwardAsync(DeviceData d } /// - public async Task ExecuteServerCommandAsync(string target, string command, Encoding encoding, CancellationToken cancellationToken = default) + public virtual async Task ExecuteServerCommandAsync(string target, string command, Encoding encoding, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(encoding); using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -172,7 +169,7 @@ public async Task ExecuteServerCommandAsync(string target, string command, Encod } /// - public async Task ExecuteServerCommandAsync(string target, string command, IAdbSocket socket, Encoding encoding, CancellationToken cancellationToken = default) + public virtual async Task ExecuteServerCommandAsync(string target, string command, IAdbSocket socket, Encoding encoding, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(encoding); @@ -188,7 +185,7 @@ public async Task ExecuteServerCommandAsync(string target, string command, IAdbS } /// - public async Task ExecuteRemoteCommandAsync(string command, DeviceData device, Encoding encoding, CancellationToken cancellationToken = default) + public virtual async Task ExecuteRemoteCommandAsync(string command, DeviceData device, Encoding encoding, CancellationToken cancellationToken = default) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(encoding); @@ -200,7 +197,7 @@ public async Task ExecuteRemoteCommandAsync(string command, DeviceData device, E } /// - public async Task ExecuteServerCommandAsync(string target, string command, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken = default) + public virtual async Task ExecuteServerCommandAsync(string target, string command, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(encoding); using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -208,7 +205,7 @@ public async Task ExecuteServerCommandAsync(string target, string command, IShel } /// - public async Task ExecuteServerCommandAsync(string target, string command, IAdbSocket socket, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken = default) + public virtual async Task ExecuteServerCommandAsync(string target, string command, IAdbSocket socket, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(encoding); @@ -233,10 +230,8 @@ public async Task ExecuteServerCommandAsync(string target, string command, IAdbS while (!cancellationToken.IsCancellationRequested) { string? line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); - if (line == null) { break; } - - receiver?.AddOutput(line); + if (receiver?.AddOutput(line) is false) { break; } } } catch (Exception e) @@ -256,7 +251,7 @@ public async Task ExecuteServerCommandAsync(string target, string command, IAdbS } /// - public async Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken = default) + public virtual async Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken = default) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(encoding); @@ -268,7 +263,7 @@ public async Task ExecuteRemoteCommandAsync(string command, DeviceData device, I } /// - public async Task GetFrameBufferAsync(DeviceData device, CancellationToken cancellationToken = default) + public virtual async Task GetFrameBufferAsync(DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -280,7 +275,7 @@ public async Task GetFrameBufferAsync(DeviceData device, Cancellati } /// - public async Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) + public virtual async Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(messageSink); @@ -331,7 +326,7 @@ public async Task RunLogServiceAsync(DeviceData device, Action message } /// - public async Task RebootAsync(string into, DeviceData device, CancellationToken cancellationToken = default) + public virtual async Task RebootAsync(string into, DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -343,7 +338,7 @@ public async Task RebootAsync(string into, DeviceData device, CancellationToken } /// - public async Task PairAsync(DnsEndPoint endpoint, string code, CancellationToken cancellationToken = default) + public virtual async Task PairAsync(DnsEndPoint endpoint, string code, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(endpoint); @@ -355,7 +350,7 @@ public async Task PairAsync(DnsEndPoint endpoint, string code, Cancellat } /// - public async Task ConnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken = default) + public virtual async Task ConnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(endpoint); @@ -367,7 +362,7 @@ public async Task ConnectAsync(DnsEndPoint endpoint, CancellationToken c } /// - public async Task DisconnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken = default) + public virtual async Task DisconnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(endpoint); @@ -379,10 +374,10 @@ public async Task DisconnectAsync(DnsEndPoint endpoint, CancellationToke } /// - public Task RootAsync(DeviceData device, CancellationToken cancellationToken = default) => RootAsync("root:", device, cancellationToken); + public virtual Task RootAsync(DeviceData device, CancellationToken cancellationToken = default) => RootAsync("root:", device, cancellationToken); /// - public Task UnrootAsync(DeviceData device, CancellationToken cancellationToken = default) => RootAsync("unroot:", device, cancellationToken); + public virtual Task UnrootAsync(DeviceData device, CancellationToken cancellationToken = default) => RootAsync("unroot:", device, cancellationToken); /// /// Restarts the ADB daemon running on the device with or without root privileges. @@ -427,8 +422,10 @@ protected async Task RootAsync(string request, DeviceData device, CancellationTo } /// - public async Task InstallAsync(DeviceData device, Stream apk, CancellationToken cancellationToken, params string[] arguments) + public virtual async Task InstallAsync(DeviceData device, Stream apk, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); @@ -460,15 +457,24 @@ public async Task InstallAsync(DeviceData device, Stream apk, CancellationToken byte[] buffer = new byte[32 * 1024]; int read = 0; + long totalBytesToProcess = apk.Length; + long totalBytesRead = 0; + +#if HAS_BUFFERS while ((read = await apk.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0) { -#if HAS_BUFFERS await socket.SendAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); #else + while ((read = await apk.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0) + { await socket.SendAsync(buffer, read, cancellationToken).ConfigureAwait(false); #endif + totalBytesRead += read; + progress?.Report(new InstallProgressEventArgs(0, 1, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess)); } + progress?.Report(new InstallProgressEventArgs(1, 1, 100)); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); string value = #if HAS_BUFFERS @@ -481,79 +487,109 @@ public async Task InstallAsync(DeviceData device, Stream apk, CancellationToken { throw new AdbException(value); } + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public async Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, CancellationToken cancellationToken, params string[] arguments) + public virtual async Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(baseAPK); ExceptionExtensions.ThrowIfNull(splitAPKs); - ExceptionExtensions.ThrowIfNull(packageName); - string session = await InstallCreateAsync(device, packageName, cancellationToken, arguments).ConfigureAwait(false); + if (!baseAPK.CanRead || !baseAPK.CanSeek) + { + throw new ArgumentOutOfRangeException(nameof(baseAPK), "The apk stream must be a readable and seekable stream"); + } - int i = 0; - await Extensions.WhenAll(splitAPKs.Select(async splitAPK => + if (splitAPKs.Any(apk => apk == null || !apk.CanRead || !apk.CanSeek)) { - if (splitAPK == null || !splitAPK.CanRead || !splitAPK.CanSeek) - { - Debug.WriteLine("The apk stream must be a readable and seekable stream"); - return; - } + throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable and seekable stream"); + } - try - { - await InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + string session = await InstallCreateAsync(device, null, cancellationToken, arguments).ConfigureAwait(false); + + int splitAPKsCount = splitAPKs.Count(); + void OnMainSyncProgressChanged(string? sender, double args) => + progress?.Report(new InstallProgressEventArgs(sender is null ? 1 : 0, splitAPKsCount + 1, args / 2)); + + await InstallWriteAsync(device, baseAPK, nameof(baseAPK), session, OnMainSyncProgressChanged, cancellationToken).ConfigureAwait(false); + + int progressCount = 1; + Dictionary status = new(splitAPKsCount); + void OnSplitSyncProgressChanged(string? sender, double args) + { + lock (status) { - Debug.WriteLine(ex.Message); + if (sender is null) + { + progressCount++; + } + else if (sender is string path) + { + status[path] = args; + } + progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); } - })).ConfigureAwait(false); + } + + int i = 0; + await Extensions.WhenAll(splitAPKs.Select(splitAPK => InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSplitSyncProgressChanged, cancellationToken))).ConfigureAwait(false); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); await InstallCommitAsync(device, session, cancellationToken).ConfigureAwait(false); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public async Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, CancellationToken cancellationToken, params string[] arguments) + public virtual async Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + EnsureDevice(device); - ExceptionExtensions.ThrowIfNull(baseAPK); ExceptionExtensions.ThrowIfNull(splitAPKs); + ExceptionExtensions.ThrowIfNull(packageName); - if (!baseAPK.CanRead || !baseAPK.CanSeek) + if (splitAPKs.Any(apk => apk == null || !apk.CanRead || !apk.CanSeek)) { - throw new ArgumentOutOfRangeException(nameof(baseAPK), "The apk stream must be a readable and seekable stream"); + throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable and seekable stream"); } - string session = await InstallCreateAsync(device, null, cancellationToken, arguments).ConfigureAwait(false); - - await InstallWriteAsync(device, baseAPK, nameof(baseAPK), session, cancellationToken).ConfigureAwait(false); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + string session = await InstallCreateAsync(device, packageName, cancellationToken, arguments).ConfigureAwait(false); - int i = 0; - await Extensions.WhenAll(splitAPKs.Select(async splitAPK => + int progressCount = 0; + int splitAPKsCount = splitAPKs.Count(); + Dictionary status = new(splitAPKsCount); + void OnSyncProgressChanged(string? sender, double args) { - if (splitAPK == null || !splitAPK.CanRead || !splitAPK.CanSeek) + lock (status) { - Debug.WriteLine("The apk stream must be a readable and seekable stream"); - return; + if (sender is null) + { + progressCount++; + } + else if (sender is string path) + { + status[path] = args; + } + progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount, status.Values.Select(x => x / splitAPKsCount).Sum())); } + } - try - { - await InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, cancellationToken).ConfigureAwait(false); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } - })).ConfigureAwait(false); + int i = 0; + await Extensions.WhenAll(splitAPKs.Select(splitAPK => InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSyncProgressChanged, cancellationToken))).ConfigureAwait(false); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); await InstallCommitAsync(device, session, cancellationToken).ConfigureAwait(false); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public async Task InstallCreateAsync(DeviceData device, string? packageName, CancellationToken cancellationToken, params string[] arguments) + public virtual async Task InstallCreateAsync(DeviceData device, string? packageName, CancellationToken cancellationToken, params string[] arguments) { EnsureDevice(device); @@ -593,8 +629,10 @@ public async Task InstallCreateAsync(DeviceData device, string? packageN } /// - public async Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, CancellationToken cancellationToken = default) + public virtual async Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, IProgress? progress = null, CancellationToken cancellationToken = default) { + progress?.Report(0); + EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); ExceptionExtensions.ThrowIfNull(apkName); @@ -621,13 +659,20 @@ public async Task InstallWriteAsync(DeviceData device, Stream apk, string apkNam byte[] buffer = new byte[32 * 1024]; int read = 0; + long totalBytesToProcess = apk.Length; + long totalBytesRead = 0; + +#if HAS_BUFFERS while ((read = await apk.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0) { -#if HAS_BUFFERS await socket.SendAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); #else + while ((read = await apk.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0) + { await socket.SendAsync(buffer, read, cancellationToken).ConfigureAwait(false); #endif + totalBytesRead += read; + progress?.Report(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); } read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); @@ -642,41 +687,40 @@ public async Task InstallWriteAsync(DeviceData device, Stream apk, string apkNam { throw new AdbException(value); } + progress?.Report(100); } - /// - public async Task InstallCommitAsync(DeviceData device, string session, CancellationToken cancellationToken = default) + /// + /// Asynchronously write an apk into the given install session. + /// + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + protected virtual async Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, Action? progress, CancellationToken cancellationToken = default) { - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"exec:cmd package 'install-commit' {session}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); + progress?.Invoke(apkName, 0); - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string? result = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); - if (result?.Contains("Success") != true) - { - throw new AdbException(await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false)); - } - } - - /// - public async Task UninstallAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) - { EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(apk); + ExceptionExtensions.ThrowIfNull(apkName); + ExceptionExtensions.ThrowIfNull(session); - StringBuilder requestBuilder = new StringBuilder().Append("exec:cmd package 'uninstall'"); - - if (arguments != null) + if (!apk.CanRead || !apk.CanSeek) { - foreach (string argument in arguments) - { - _ = requestBuilder.AppendFormat(" {0}", argument); - } + throw new ArgumentOutOfRangeException(nameof(apk), "The apk stream must be a readable and seekable stream"); } - _ = requestBuilder.AppendFormat(" {0}", packageName); + StringBuilder requestBuilder = + new StringBuilder().Append($"exec:cmd package 'install-write'") + // add size parameter [required for streaming installs] + // do last to override any user specified value + .AppendFormat(" -S {0}", apk.Length) + .AppendFormat(" {0} {1}.apk", session, apkName); using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); @@ -684,441 +728,101 @@ public async Task UninstallAsync(DeviceData device, string packageName, Cancella await socket.SendAdbRequestAsync(requestBuilder.ToString(), cancellationToken).ConfigureAwait(false); _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string? result = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); - if (result?.Contains("Success") != true) - { - throw new AdbException(await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false)); - } - } - - /// - public async Task> GetFeatureSetAsync(DeviceData device, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SendAdbRequestAsync($"host-serial:{device.Serial}:features", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - string features = await socket.ReadStringAsync(cancellationToken).ConfigureAwait(false); - - IEnumerable featureList = features.Trim().Split('\n', ','); - return featureList; - } - - /// - public async Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync("shell:uiautomator dump /dev/tty", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string xmlString = - reader.ReadToEnd() - .Replace("Events injected: 1\r\n", string.Empty) - .Replace("UI hierchary dumped to: /dev/tty", string.Empty) - .Trim(); - - if (string.IsNullOrEmpty(xmlString) || xmlString.StartsWith(" - public async Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - string xmlString = await DumpScreenStringAsync(device, cancellationToken).ConfigureAwait(false); - XmlDocument doc = new(); - if (!string.IsNullOrEmpty(xmlString)) +#if HAS_BUFFERS + while ((read = await apk.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0) { - doc.LoadXml(xmlString); - return doc; - } - return null; - } - -#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER - /// - public async Task DumpScreenWinRTAsync(DeviceData device, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - string xmlString = await DumpScreenStringAsync(device, cancellationToken).ConfigureAwait(false); - Windows.Data.Xml.Dom.XmlDocument doc = new(); - if (!string.IsNullOrEmpty(xmlString)) + await socket.SendAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false); +#else + while ((read = await apk.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0) { - doc.LoadXml(xmlString); - return doc; - } - return null; - } + await socket.SendAsync(buffer, read, cancellationToken).ConfigureAwait(false); #endif - - /// - public async Task ClickAsync(DeviceData device, Point cords, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:input tap {cords.X} {cords.Y}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart()).ConfigureAwait(false); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); + totalBytesRead += read; + progress?.Invoke(apkName, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR - { - throw new ElementNotFoundException("Coordinates of element is invalid"); - } - } - - /// - public async Task ClickAsync(DeviceData device, int x, int y, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:input tap {x} {y}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart()).ConfigureAwait(false); + progress?.Invoke(apkName, 100); - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR - { - throw new ElementNotFoundException("Coordinates of element is invalid"); - } - } - - /// - public async Task SwipeAsync(DeviceData device, Element first, Element second, long speed, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:input swipe {first.Center.X} {first.Center.Y} {second.Center.X} {second.Center.Y} {speed}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart()).ConfigureAwait(false); + read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + string value = +#if HAS_BUFFERS + Encoding.UTF8.GetString(buffer.AsSpan(0, read)); +#else + Encoding.UTF8.GetString(buffer, 0, read); +#endif - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + if (!value.Contains("Success")) { - throw new ElementNotFoundException("Coordinates of element is invalid"); + throw new AdbException(value); } + progress?.Invoke(null, 100); } /// - public async Task SwipeAsync(DeviceData device, int x1, int y1, int x2, int y2, long speed, CancellationToken cancellationToken = default) + public virtual async Task InstallCommitAsync(DeviceData device, string session, CancellationToken cancellationToken = default) { - EnsureDevice(device); - using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - await socket.SendAdbRequestAsync($"shell:input swipe {x1} {y1} {x2} {y2} {speed}", cancellationToken).ConfigureAwait(false); + await socket.SendAdbRequestAsync($"exec:cmd package 'install-commit' {session}", cancellationToken).ConfigureAwait(false); _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart()).ConfigureAwait(false); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + string? result = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); + if (result?.Contains("Success") != true) { - throw new ElementNotFoundException("Coordinates of element is invalid"); + throw new AdbException(await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false)); } } /// - public async Task IsAppRunningAsync(DeviceData device, string packageName, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:pidof {packageName}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string? result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart().Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()).ConfigureAwait(false); - bool intParsed = int.TryParse(result, out int pid); - return intParsed && pid > 0; - } - - /// - public async Task IsAppInForegroundAsync(DeviceData device, string packageName, CancellationToken cancellationToken = default) + public virtual async Task UninstallAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) { EnsureDevice(device); - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:dumpsys activity activities | grep mResumedActivity", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false); - return result.Contains(packageName); - } - - /// - public async Task GetAppStatusAsync(DeviceData device, string packageName, CancellationToken cancellationToken = default) - { - // Check if the app is in foreground - bool currentApp = await IsAppInForegroundAsync(device, packageName, cancellationToken).ConfigureAwait(false); - if (currentApp) - { - return AppStatus.Foreground; - } - - // Check if the app is running in background - bool isAppRunning = await IsAppRunningAsync(device, packageName, cancellationToken).ConfigureAwait(false); - return isAppRunning ? AppStatus.Background : AppStatus.Stopped; - } - - /// - public async Task FindElementAsync(DeviceData device, string xpath = "hierarchy/node", CancellationToken cancellationToken = default) - { - try - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - XmlDocument? doc = await DumpScreenAsync(device, cancellationToken).ConfigureAwait(false); - if (doc != null) - { - XmlNode? xmlNode = doc.SelectSingleNode(xpath); - if (xmlNode != null) - { - Element? element = Element.FromXmlNode(this, device, xmlNode); - if (element != null) - { - return element; - } - } - } - } - catch (XmlException) - { - // Ignore XmlException and try again - } - if (cancellationToken == default) { break; } - } - } - catch (Exception e) - { - // If a cancellation was requested, this main loop is interrupted with an exception - // because the socket is closed. In that case, we don't need to throw a ShellCommandUnresponsiveException. - // In all other cases, something went wrong, and we want to report it to the user. - if (!cancellationToken.IsCancellationRequested) - { - throw new ShellCommandUnresponsiveException(e); - } - } - return null; - } - - /// - public async Task> FindElementsAsync(DeviceData device, string xpath = "hierarchy/node", CancellationToken cancellationToken = default) - { - try - { - while (!cancellationToken.IsCancellationRequested) - { - try - { - XmlDocument? doc = await DumpScreenAsync(device, cancellationToken).ConfigureAwait(false); - if (doc != null) - { - XmlNodeList? xmlNodes = doc.SelectNodes(xpath); - if (xmlNodes != null) - { - static IEnumerable FindElements(IAdbClient client, DeviceData device, XmlNodeList xmlNodes) - { - for (int i = 0; i < xmlNodes.Count; i++) - { - Element? element = Element.FromXmlNode(client, device, xmlNodes[i]); - if (element != null) - { - yield return element; - } - } - } - return FindElements(this, device, xmlNodes); - } - } - } - catch (XmlException) - { - // Ignore XmlException and try again - } - if (cancellationToken == default) { break; } - } - } - catch (Exception e) - { - // If a cancellation was requested, this main loop is interrupted with an exception - // because the socket is closed. In that case, we don't need to throw a ShellCommandUnresponsiveException. - // In all other cases, something went wrong, and we want to report it to the user. - if (!cancellationToken.IsCancellationRequested) - { - throw new ShellCommandUnresponsiveException(e); - } - } - return Enumerable.Empty(); - } + StringBuilder requestBuilder = new StringBuilder().Append("exec:cmd package 'uninstall'"); -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - /// - public async IAsyncEnumerable FindAsyncElements(DeviceData device, string xpath = "hierarchy/node", [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - while (!cancellationToken.IsCancellationRequested) + if (arguments != null) { - XmlDocument? doc = null; - - try - { - doc = await DumpScreenAsync(device, cancellationToken).ConfigureAwait(false); - } - catch (XmlException) - { - // Ignore XmlException and try again - } - catch (Exception e) - { - // If a cancellation was requested, this main loop is interrupted with an exception - // because the socket is closed. In that case, we don't need to throw a ShellCommandUnresponsiveException. - // In all other cases, something went wrong, and we want to report it to the user. - if (!cancellationToken.IsCancellationRequested) - { - throw new ShellCommandUnresponsiveException(e); - } - } - - if (doc != null) + foreach (string argument in arguments) { - XmlNodeList? xmlNodes = doc.SelectNodes(xpath); - if (xmlNodes != null) - { - for (int i = 0; i < xmlNodes.Count; i++) - { - Element? element = Element.FromXmlNode(this, device, xmlNodes[i]); - if (element != null) - { - yield return element; - } - } - break; - } + _ = requestBuilder.AppendFormat(" {0}", argument); } - - if (cancellationToken == default) { break; } - } - } -#endif - - /// - public async Task SendKeyEventAsync(DeviceData device, string key, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:input keyevent {key}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart()).ConfigureAwait(false); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR - { - throw new InvalidKeyEventException("KeyEvent is invalid"); } - } - /// - public async Task SendTextAsync(DeviceData device, string text, CancellationToken cancellationToken = default) - { - EnsureDevice(device); + _ = requestBuilder.AppendFormat(" {0}", packageName); using IAdbSocket socket = adbSocketFactory(EndPoint); await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - await socket.SendAdbRequestAsync($"shell:input text {text}", cancellationToken).ConfigureAwait(false); + await socket.SendAdbRequestAsync(requestBuilder.ToString(), cancellationToken).ConfigureAwait(false); _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = await reader.ReadToEndAsync(cancellationToken).ContinueWith(x => x.Result.TrimStart()).ConfigureAwait(false); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + string? result = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false); + if (result?.Contains("Success") != true) { - throw new InvalidTextException(); + throw new AdbException(await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false)); } } /// - public async Task StartAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken = default) + public virtual async Task> GetFeatureSetAsync(DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); - - await socket.SendAdbRequestAsync($"shell:monkey -p {packageName} 1", cancellationToken).ConfigureAwait(false); + await socket.SendAdbRequestAsync($"host-serial:{device.Serial}:features", cancellationToken).ConfigureAwait(false); _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); - } - - /// - public async Task StopAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken = default) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); + string features = await socket.ReadStringAsync(cancellationToken).ConfigureAwait(false); - await socket.SendAdbRequestAsync($"shell:am force-stop {packageName}", cancellationToken).ConfigureAwait(false); - _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); + IEnumerable featureList = features.Trim().Split('\n', ','); + return featureList; } } } diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 59ebd5d0..fb789e12 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -5,16 +5,13 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Text; -using System.Text.RegularExpressions; using System.Threading; -using System.Xml; namespace AdvancedSharpAdbClient { @@ -170,7 +167,7 @@ public static byte[] CreateAdbForwardRequest(string address, int port) } /// - public int GetAdbVersion() + public virtual int GetAdbVersion() { using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -182,7 +179,7 @@ public int GetAdbVersion() } /// - public void KillAdb() + public virtual void KillAdb() { using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SendAdbRequest("host:kill"); @@ -192,7 +189,7 @@ public void KillAdb() } /// - public IEnumerable GetDevices() + public virtual IEnumerable GetDevices() { using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -205,7 +202,7 @@ public IEnumerable GetDevices() } /// - public int CreateForward(DeviceData device, string local, string remote, bool allowRebind) + public virtual int CreateForward(DeviceData device, string local, string remote, bool allowRebind) { EnsureDevice(device); @@ -221,7 +218,7 @@ public int CreateForward(DeviceData device, string local, string remote, bool al } /// - public int CreateReverseForward(DeviceData device, string remote, string local, bool allowRebind) + public virtual int CreateReverseForward(DeviceData device, string remote, string local, bool allowRebind) { EnsureDevice(device); @@ -239,7 +236,7 @@ public int CreateReverseForward(DeviceData device, string remote, string local, } /// - public void RemoveReverseForward(DeviceData device, string remote) + public virtual void RemoveReverseForward(DeviceData device, string remote) { EnsureDevice(device); @@ -251,7 +248,7 @@ public void RemoveReverseForward(DeviceData device, string remote) } /// - public void RemoveAllReverseForwards(DeviceData device) + public virtual void RemoveAllReverseForwards(DeviceData device) { EnsureDevice(device); @@ -263,7 +260,7 @@ public void RemoveAllReverseForwards(DeviceData device) } /// - public void RemoveForward(DeviceData device, int localPort) + public virtual void RemoveForward(DeviceData device, int localPort) { EnsureDevice(device); @@ -273,7 +270,7 @@ public void RemoveForward(DeviceData device, int localPort) } /// - public void RemoveAllForwards(DeviceData device) + public virtual void RemoveAllForwards(DeviceData device) { EnsureDevice(device); @@ -313,7 +310,7 @@ public IEnumerable ListReverseForward(DeviceData device) } /// - public void ExecuteServerCommand(string target, string command, Encoding encoding) + public virtual void ExecuteServerCommand(string target, string command, Encoding encoding) { ExceptionExtensions.ThrowIfNull(encoding); using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -337,7 +334,7 @@ public void ExecuteServerCommand(string target, string command, IAdbSocket socke } /// - public void ExecuteRemoteCommand(string command, DeviceData device, Encoding encoding) + public virtual void ExecuteRemoteCommand(string command, DeviceData device, Encoding encoding) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(encoding); @@ -349,7 +346,7 @@ public void ExecuteRemoteCommand(string command, DeviceData device, Encoding enc } /// - public void ExecuteServerCommand(string target, string command, IShellOutputReceiver receiver, Encoding encoding) + public virtual void ExecuteServerCommand(string target, string command, IShellOutputReceiver receiver, Encoding encoding) { ExceptionExtensions.ThrowIfNull(encoding); using IAdbSocket socket = adbSocketFactory(EndPoint); @@ -382,7 +379,7 @@ public void ExecuteServerCommand(string target, string command, IAdbSocket socke { string? line = reader.ReadLine(); if (line == null) { break; } - receiver?.AddOutput(line); + if (receiver?.AddOutput(line) is false) { break; } } } catch (Exception e) @@ -396,7 +393,7 @@ public void ExecuteServerCommand(string target, string command, IAdbSocket socke } /// - public void ExecuteRemoteCommand(string command, DeviceData device, IShellOutputReceiver receiver, Encoding encoding) + public virtual void ExecuteRemoteCommand(string command, DeviceData device, IShellOutputReceiver receiver, Encoding encoding) { EnsureDevice(device); @@ -426,7 +423,7 @@ public Framebuffer GetFrameBuffer(DeviceData device) } /// - public void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames) + public virtual void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(messageSink); @@ -474,7 +471,7 @@ public void RunLogService(DeviceData device, Action messageSink, param } /// - public void Reboot(string into, DeviceData device) + public virtual void Reboot(string into, DeviceData device) { EnsureDevice(device); @@ -571,8 +568,10 @@ protected void Root(string request, DeviceData device) } /// - public void Install(DeviceData device, Stream apk, params string[] arguments) + public void Install(DeviceData device, Stream apk, IProgress? progress = null, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); @@ -604,15 +603,24 @@ public void Install(DeviceData device, Stream apk, params string[] arguments) byte[] buffer = new byte[32 * 1024]; int read = 0; + long totalBytesToProcess = apk.Length; + long totalBytesRead = 0; + +#if HAS_BUFFERS while ((read = apk.Read(buffer)) > 0) { -#if HAS_BUFFERS socket.Send(buffer.AsSpan(0, read)); #else + while ((read = apk.Read(buffer, 0, buffer.Length)) > 0) + { socket.Send(buffer, read); #endif + totalBytesRead += read; + progress?.Report(new InstallProgressEventArgs(0, 1, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess)); } + progress?.Report(new InstallProgressEventArgs(1, 1, 100)); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); read = socket.Read(buffer); string value = #if HAS_BUFFERS @@ -625,75 +633,111 @@ public void Install(DeviceData device, Stream apk, params string[] arguments) { throw new AdbException(value); } + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, params string[] arguments) + public void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(baseAPK); ExceptionExtensions.ThrowIfNull(splitAPKs); - ExceptionExtensions.ThrowIfNull(packageName); - string session = InstallCreate(device, packageName, arguments); + if (!baseAPK.CanRead || !baseAPK.CanSeek) + { + throw new ArgumentOutOfRangeException(nameof(baseAPK), "The apk stream must be a readable and seekable stream"); + } - int i = 0; - foreach (Stream splitAPK in splitAPKs) + if (splitAPKs.Any(apk => apk == null || !apk.CanRead || !apk.CanSeek)) { - if (splitAPK == null || !splitAPK.CanRead || !splitAPK.CanSeek) - { - Debug.WriteLine("The apk stream must be a readable and seekable stream"); - continue; - } + throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable and seekable stream"); + } - try - { - InstallWrite(device, splitAPK, $"{nameof(splitAPK)}{i++}", session); - } - catch (Exception ex) + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + string session = InstallCreate(device, null, arguments); + + int splitAPKsCount = splitAPKs.Count(); + void OnMainSyncProgressChanged(string? sender, double args) => + progress?.Report(new InstallProgressEventArgs(sender is null ? 1 : 0, splitAPKsCount + 1, args / 2)); + + InstallWrite(device, baseAPK, nameof(baseAPK), session, OnMainSyncProgressChanged); + + int progressCount = 1; + Dictionary status = new(splitAPKsCount); + void OnSplitSyncProgressChanged(string? sender, double args) + { + lock (status) { - Debug.WriteLine(ex.Message); + if (sender is null) + { + progressCount++; + } + else if (sender is string path) + { + status[path] = args; + } + progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); } } + int i = 0; + foreach (Stream splitAPK in splitAPKs) + { + InstallWrite(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSplitSyncProgressChanged); + } + + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallCommit(device, session); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, params string[] arguments) + public void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + EnsureDevice(device); - ExceptionExtensions.ThrowIfNull(baseAPK); ExceptionExtensions.ThrowIfNull(splitAPKs); + ExceptionExtensions.ThrowIfNull(packageName); - if (!baseAPK.CanRead || !baseAPK.CanSeek) + if (splitAPKs.Any(apk => apk == null || !apk.CanRead || !apk.CanSeek)) { - throw new ArgumentOutOfRangeException(nameof(baseAPK), "The apk stream must be a readable and seekable stream"); + throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable and seekable stream"); } - string session = InstallCreate(device, null, arguments); - - InstallWrite(device, baseAPK, nameof(baseAPK), session); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + string session = InstallCreate(device, packageName, arguments); - int i = 0; - foreach (Stream splitAPK in splitAPKs) + int progressCount = 0; + int splitAPKsCount = splitAPKs.Count(); + Dictionary status = new(splitAPKsCount); + void OnSyncProgressChanged(string? sender, double args) { - if (splitAPK == null || !splitAPK.CanRead || !splitAPK.CanSeek) + lock (status) { - Debug.WriteLine("The apk stream must be a readable and seekable stream"); - continue; + if (sender is null) + { + progressCount++; + } + else if (sender is string path) + { + status[path] = args; + } + progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount, status.Values.Select(x => x / splitAPKsCount).Sum())); } + } - try - { - InstallWrite(device, splitAPK, $"{nameof(splitAPK)}{i++}", session); - } - catch (Exception ex) - { - Debug.WriteLine(ex.Message); - } + int i = 0; + foreach (Stream splitAPK in splitAPKs) + { + InstallWrite(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSyncProgressChanged); } + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallCommit(device, session); + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -736,8 +780,10 @@ public string InstallCreate(DeviceData device, string? packageName = null, param } /// - public void InstallWrite(DeviceData device, Stream apk, string apkName, string session) + public void InstallWrite(DeviceData device, Stream apk, string apkName, string session, IProgress? progress = null) { + progress?.Report(0); + EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); ExceptionExtensions.ThrowIfNull(apkName); @@ -764,13 +810,20 @@ public void InstallWrite(DeviceData device, Stream apk, string apkName, string s byte[] buffer = new byte[32 * 1024]; int read = 0; + long totalBytesToProcess = apk.Length; + long totalBytesRead = 0; + +#if HAS_BUFFERS while ((read = apk.Read(buffer)) > 0) { -#if HAS_BUFFERS socket.Send(buffer.AsSpan(0, read)); #else + while ((read = apk.Read(buffer, 0, buffer.Length)) > 0) + { socket.Send(buffer, read); #endif + totalBytesRead += read; + progress?.Report(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); } read = socket.Read(buffer); @@ -785,43 +838,37 @@ public void InstallWrite(DeviceData device, Stream apk, string apkName, string s { throw new AdbException(value); } + progress?.Report(100); } - /// - public void InstallCommit(DeviceData device, string session) + /// + /// Write an apk into the given install session. + /// + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + protected virtual void InstallWrite(DeviceData device, Stream apk, string apkName, string session, Action? progress) { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"exec:cmd package 'install-commit' {session}"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string? result = reader.ReadLine(); - if (result?.Contains("Success") != true) - { - throw new AdbException(reader.ReadToEnd()); - } - } + progress?.Invoke(apkName, 0); - /// - public void Uninstall(DeviceData device, string packageName, params string[] arguments) - { EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(apk); + ExceptionExtensions.ThrowIfNull(apkName); + ExceptionExtensions.ThrowIfNull(session); - StringBuilder requestBuilder = new StringBuilder().Append("exec:cmd package 'uninstall'"); - - if (arguments != null) + if (!apk.CanRead || !apk.CanSeek) { - foreach (string argument in arguments) - { - _ = requestBuilder.AppendFormat(" {0}", argument); - } + throw new ArgumentOutOfRangeException(nameof(apk), "The apk stream must be a readable and seekable stream"); } - _ = requestBuilder.AppendFormat(" {0}", packageName); + StringBuilder requestBuilder = + new StringBuilder().Append($"exec:cmd package 'install-write'") + // add size parameter [required for streaming installs] + // do last to override any user specified value + .AppendFormat(" -S {0}", apk.Length) + .AppendFormat(" {0} {1}.apk", session, apkName); using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); @@ -829,374 +876,103 @@ public void Uninstall(DeviceData device, string packageName, params string[] arg socket.SendAdbRequest(requestBuilder.ToString()); _ = socket.ReadAdbResponse(); - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string? result = reader.ReadLine(); - if (result?.Contains("Success") != true) - { - throw new AdbException(reader.ReadToEnd()); - } - } - - /// - public IEnumerable GetFeatureSet(DeviceData device) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SendAdbRequest($"host-serial:{device.Serial}:features"); - _ = socket.ReadAdbResponse(); - string features = socket.ReadString(); - - IEnumerable featureList = features.Trim().Split('\n', ','); - return featureList; - } - - /// - public string DumpScreenString(DeviceData device) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest("shell:uiautomator dump /dev/tty"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string xmlString = reader.ReadToEnd() - .Replace("Events injected: 1\r\n", string.Empty) - .Replace("UI hierchary dumped to: /dev/tty", string.Empty) - .Trim(); - - if (string.IsNullOrEmpty(xmlString) || xmlString.StartsWith(" - public XmlDocument? DumpScreen(DeviceData device) - { - EnsureDevice(device); - XmlDocument doc = new(); - string xmlString = DumpScreenString(device); - if (!string.IsNullOrEmpty(xmlString)) +#if HAS_BUFFERS + while ((read = apk.Read(buffer)) > 0) { - doc.LoadXml(xmlString); - return doc; - } - return null; - } - -#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER - /// - public Windows.Data.Xml.Dom.XmlDocument? DumpScreenWinRT(DeviceData device) - { - EnsureDevice(device); - Windows.Data.Xml.Dom.XmlDocument doc = new(); - string xmlString = DumpScreenString(device); - if (!string.IsNullOrEmpty(xmlString)) + socket.Send(buffer.AsSpan(0, read)); +#else + while ((read = apk.Read(buffer, 0, buffer.Length)) > 0) { - doc.LoadXml(xmlString); - return doc; - } - return null; - } + socket.Send(buffer, read); #endif - - /// - public void Click(DeviceData device, Point cords) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:input tap {cords.X} {cords.Y}"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd().TrimStart(); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR - { - throw new ElementNotFoundException("Coordinates of element is invalid"); - } - } - - /// - public void Click(DeviceData device, int x, int y) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:input tap {x} {y}"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd().TrimStart(); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR - { - throw new ElementNotFoundException("Coordinates of element is invalid"); + totalBytesRead += read; + progress?.Invoke(apkName, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); } - } - - /// - public void Swipe(DeviceData device, Element first, Element second, long speed) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:input swipe {first.Center.X} {first.Center.Y} {second.Center.X} {second.Center.Y} {speed}"); - _ = socket.ReadAdbResponse(); + progress?.Invoke(apkName, 100); - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd().TrimStart(); + read = socket.Read(buffer); + string value = +#if HAS_BUFFERS + Encoding.UTF8.GetString(buffer.AsSpan(0, read)); +#else + Encoding.UTF8.GetString(buffer, 0, read); +#endif - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + if (!value.Contains("Success")) { - throw new ElementNotFoundException("Coordinates of element is invalid"); + throw new AdbException(value); } + progress?.Invoke(null, 100); } /// - public void Swipe(DeviceData device, int x1, int y1, int x2, int y2, long speed) + public virtual void InstallCommit(DeviceData device, string session) { EnsureDevice(device); using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest($"shell:input swipe {x1} {y1} {x2} {y2} {speed}"); + socket.SendAdbRequest($"exec:cmd package 'install-commit' {session}"); _ = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd().TrimStart(); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + string? result = reader.ReadLine(); + if (result?.Contains("Success") != true) { - throw new ElementNotFoundException("Coordinates of element is invalid"); + throw new AdbException(reader.ReadToEnd()); } } /// - public bool IsAppRunning(DeviceData device, string packageName) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:pidof {packageName}"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string? result = reader.ReadToEnd().TrimStart().Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); - bool intParsed = int.TryParse(result, out int pid); - return intParsed && pid > 0; - } - - /// - public bool IsAppInForeground(DeviceData device, string packageName) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:dumpsys activity activities | grep mResumedActivity"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd(); - return result.Contains(packageName); - } - - /// - public AppStatus GetAppStatus(DeviceData device, string packageName) + public virtual void Uninstall(DeviceData device, string packageName, params string[] arguments) { EnsureDevice(device); - // Check if the app is in foreground - bool currentApp = IsAppInForeground(device, packageName); - if (currentApp) - { - return AppStatus.Foreground; - } - - // Check if the app is running in background - bool isAppRunning = IsAppRunning(device, packageName); - return isAppRunning ? AppStatus.Background : AppStatus.Stopped; - } - - /// - public Element? FindElement(DeviceData device, string xpath = "hierarchy/node", TimeSpan timeout = default) - { - EnsureDevice(device); - Stopwatch stopwatch = new(); - stopwatch.Start(); - do - { - try - { - XmlDocument? doc = DumpScreen(device); - if (doc != null) - { - XmlNode? xmlNode = doc.SelectSingleNode(xpath); - if (xmlNode != null) - { - Element? element = Element.FromXmlNode(this, device, xmlNode); - if (element != null) - { - return element; - } - } - } - } - catch (XmlException) - { - // Ignore XmlException and try again - } - if (timeout == default) { break; } - } - while (stopwatch.Elapsed < timeout); - return null; - } + StringBuilder requestBuilder = new StringBuilder().Append("exec:cmd package 'uninstall'"); - /// - public IEnumerable FindElements(DeviceData device, string xpath = "hierarchy/node", TimeSpan timeout = default) - { - EnsureDevice(device); - Stopwatch stopwatch = new(); - stopwatch.Start(); - do + if (arguments != null) { - XmlDocument? doc = null; - - try - { - doc = DumpScreen(device); - } - catch (XmlException) - { - // Ignore XmlException and try again - } - - if (doc != null) + foreach (string argument in arguments) { - XmlNodeList? xmlNodes = doc.SelectNodes(xpath); - if (xmlNodes != null) - { - for (int i = 0; i < xmlNodes.Count; i++) - { - Element? element = Element.FromXmlNode(this, device, xmlNodes[i]); - if (element != null) - { - yield return element; - } - } - break; - } + _ = requestBuilder.AppendFormat(" {0}", argument); } - - if (timeout == default) { break; } } - while (stopwatch.Elapsed < timeout); - } - /// - public void SendKeyEvent(DeviceData device, string key) - { - EnsureDevice(device); + _ = requestBuilder.AppendFormat(" {0}", packageName); using IAdbSocket socket = adbSocketFactory(EndPoint); socket.SetDevice(device); - socket.SendAdbRequest($"shell:input keyevent {key}"); + socket.SendAdbRequest(requestBuilder.ToString()); _ = socket.ReadAdbResponse(); using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd().TrimStart(); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + string? result = reader.ReadLine(); + if (result?.Contains("Success") != true) { - throw new InvalidKeyEventException("KeyEvent is invalid"); + throw new AdbException(reader.ReadToEnd()); } } /// - public void SendText(DeviceData device, string text) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:input text {text}"); - _ = socket.ReadAdbResponse(); - - using StreamReader reader = new(socket.GetShellStream(), Encoding); - string result = reader.ReadToEnd().TrimStart(); - - if (result.StartsWith("java.lang.")) - { - throw JavaException.Parse(result); - } - else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR - { - throw new InvalidTextException(); - } - } - /// - public void StartApp(DeviceData device, string packageName) + public virtual IEnumerable GetFeatureSet(DeviceData device) { EnsureDevice(device); using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); - - socket.SendAdbRequest($"shell:monkey -p {packageName} 1"); + socket.SendAdbRequest($"host-serial:{device.Serial}:features"); _ = socket.ReadAdbResponse(); - } - - /// - public void StopApp(DeviceData device, string packageName) - { - EnsureDevice(device); - - using IAdbSocket socket = adbSocketFactory(EndPoint); - socket.SetDevice(device); + string features = socket.ReadString(); - socket.SendAdbRequest($"shell:am force-stop {packageName}"); - _ = socket.ReadAdbResponse(); + IEnumerable featureList = features.Trim().Split('\n', ','); + return featureList; } /// @@ -1219,13 +995,6 @@ protected static void EnsureDevice([NotNull] DeviceData? device) throw new ArgumentOutOfRangeException(nameof(device), "You must specific a serial number for the device"); } } - -#if NET7_0_OR_GREATER - [GeneratedRegex("<\\?xml(.?)*")] - private static partial Regex GetXmlRegex(); -#else - private static Regex GetXmlRegex() => new("<\\?xml(.?)*"); -#endif } /// diff --git a/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs b/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs index 05906f5f..11edbf1d 100644 --- a/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs @@ -8,17 +8,14 @@ using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Threading; namespace AdvancedSharpAdbClient { public partial class AdbCommandLineClient { - /// - /// Queries adb for its version number and checks it against . - /// - /// A which can be used to cancel the asynchronous operation. - /// A which return a object that contains the version number of the Android Command Line client. + /// public virtual async Task GetVersionAsync(CancellationToken cancellationToken = default) { // Run the adb.exe version command and capture the output. @@ -36,12 +33,8 @@ public virtual async Task GetVersionAsync(CancellationToken cancellatio return version; } - - /// - /// Starts the adb server by running the adb start-server command. - /// - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. + + /// public virtual async Task StartServerAsync(CancellationToken cancellationToken = default) { int status = await RunAdbProcessInnerAsync("start-server", null, null, cancellationToken).ConfigureAwait(false); @@ -81,8 +74,16 @@ public virtual async Task StartServerAsync(CancellationToken cancellationToken = await RunAdbProcessAsync("start-server", null, null, cancellationToken).ConfigureAwait(false); } + /// + public virtual Task CheckFileExistsAsync(string adbPath, CancellationToken cancellationToken = default) => +#if WINDOWS_UWP + StorageFile.GetFileFromPathAsync(adbPath).AsTask(cancellationToken).ContinueWith(x => x.Result != null && x.Result.IsOfType(StorageItemTypes.File)); +#else + Extensions.FromResult(File.Exists(adbPath)); +#endif + /// - /// Runs the adb.exe process, invoking a specific , + /// Asynchronously runs the adb.exe process, invoking a specific , /// and reads the standard output and standard error output. /// /// The adb.exe command to invoke, such as version or start-server. @@ -102,7 +103,7 @@ protected virtual async Task RunAdbProcessAsync(string command, ICollection - /// Runs the adb.exe process, invoking a specific , + /// Asynchronously runs the adb.exe process, invoking a specific , /// and reads the standard output and standard error output. /// /// The adb.exe command to invoke, such as version or start-server. @@ -121,7 +122,7 @@ protected virtual async Task RunAdbProcessInnerAsync(string command, IColle } /// - /// Runs process, invoking a specific command, and reads the standard output and standard error output. + /// Asynchronously runs process, invoking a specific command, and reads the standard output and standard error output. /// /// The return code of the process. #if !HAS_PROCESS diff --git a/AdvancedSharpAdbClient/AdbCommandLineClient.cs b/AdvancedSharpAdbClient/AdbCommandLineClient.cs index 9c9c5b66..293d4012 100644 --- a/AdvancedSharpAdbClient/AdbCommandLineClient.cs +++ b/AdvancedSharpAdbClient/AdbCommandLineClient.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.RegularExpressions; +using System.Threading; namespace AdvancedSharpAdbClient { @@ -61,10 +62,7 @@ public AdbCommandLineClient(string adbPath, bool isForce = false, ILogger public string AdbPath { get; protected set; } - /// - /// Queries adb for its version number and checks it against . - /// - /// A object that contains the version number of the Android Command Line client. + /// public virtual Version GetVersion() { // Run the adb.exe version command and capture the output. @@ -83,9 +81,7 @@ public virtual Version GetVersion() return version; } - /// - /// Starts the adb server by running the adb start-server command. - /// + /// public virtual void StartServer() { int status = RunAdbProcessInner("start-server", null, null); @@ -126,7 +122,12 @@ public virtual void StartServer() } /// - public virtual bool CheckFileExists(string adbPath) => Factories.CheckFileExists(adbPath); + public virtual bool CheckFileExists(string adbPath) => +#if WINDOWS_UWP + StorageFile.GetFileFromPathAsync(adbPath).GetResults() is StorageFile file && file.IsOfType(StorageItemTypes.File); +#else + File.Exists(adbPath); +#endif /// /// Throws an error if the path does not point to a valid instance of adb.exe. diff --git a/AdvancedSharpAdbClient/AdbServer.Async.cs b/AdvancedSharpAdbClient/AdbServer.Async.cs index bfbf35bd..0ca5e40d 100644 --- a/AdvancedSharpAdbClient/AdbServer.Async.cs +++ b/AdvancedSharpAdbClient/AdbServer.Async.cs @@ -22,9 +22,8 @@ public virtual async Task StartServerAsync(string adbPath, bo Version? commandLineVersion = null; IAdbCommandLineClient commandLineClient = adbCommandLineClientFactory(adbPath); - CheckFileExists = commandLineClient.CheckFileExists; - if (commandLineClient.CheckFileExists(adbPath)) + if (await commandLineClient.CheckFileExistsAsync(adbPath, cancellationToken).ConfigureAwait(false)) { CachedAdbPath = adbPath; commandLineVersion = await commandLineClient.GetVersionAsync(cancellationToken).ConfigureAwait(false); @@ -36,8 +35,8 @@ public virtual async Task StartServerAsync(string adbPath, bo return !serverStatus.IsRunning ? throw new AdbException("The adb server is not running, but no valid path to the adb.exe executable was provided. The adb server cannot be started.") : serverStatus.Version >= RequiredAdbVersion - ? StartServerResult.AlreadyRunning - : throw new AdbException($"The adb daemon is running an outdated version ${commandLineVersion}, but not valid path to the adb.exe executable was provided. A more recent version of the adb server cannot be started."); + ? StartServerResult.AlreadyRunning + : throw new AdbException($"The adb daemon is running an outdated version ${commandLineVersion}, but not valid path to the adb.exe executable was provided. A more recent version of the adb server cannot be started."); } if (serverStatus.IsRunning) @@ -45,8 +44,6 @@ public virtual async Task StartServerAsync(string adbPath, bo if (serverStatus.Version < RequiredAdbVersion || (restartServerIfNewer && serverStatus.Version < commandLineVersion)) { - ExceptionExtensions.ThrowIfNull(adbPath); - await StopServerAsync(cancellationToken); await commandLineClient.StartServerAsync(cancellationToken); return StartServerResult.RestartedOutdatedDaemon; @@ -58,8 +55,6 @@ public virtual async Task StartServerAsync(string adbPath, bo } else { - ExceptionExtensions.ThrowIfNull(adbPath); - await commandLineClient.StartServerAsync(cancellationToken); return StartServerResult.Started; } diff --git a/AdvancedSharpAdbClient/AdbServer.cs b/AdvancedSharpAdbClient/AdbServer.cs index 1ac66757..714d7120 100644 --- a/AdvancedSharpAdbClient/AdbServer.cs +++ b/AdvancedSharpAdbClient/AdbServer.cs @@ -154,11 +154,6 @@ public AdbServer(Func adbSocketFactory, Func public static IAdbServer Instance { get; set; } = new AdbServer(); - /// - /// Throws an error if the path does not point to a valid instance of adb.exe. - /// - protected static Func CheckFileExists { get; set; } = Factories.CheckFileExists; - /// /// if is starting adb server; otherwise, . /// @@ -179,7 +174,6 @@ public virtual StartServerResult StartServer(string adbPath, bool restartServerI Version? commandLineVersion = null; IAdbCommandLineClient commandLineClient = adbCommandLineClientFactory(adbPath); - CheckFileExists = commandLineClient.CheckFileExists; if (commandLineClient.CheckFileExists(adbPath)) { @@ -193,8 +187,8 @@ public virtual StartServerResult StartServer(string adbPath, bool restartServerI return !serverStatus.IsRunning ? throw new AdbException("The adb server is not running, but no valid path to the adb.exe executable was provided. The adb server cannot be started.") : serverStatus.Version >= RequiredAdbVersion - ? StartServerResult.AlreadyRunning - : throw new AdbException($"The adb daemon is running an outdated version ${commandLineVersion}, but not valid path to the adb.exe executable was provided. A more recent version of the adb server cannot be started."); + ? StartServerResult.AlreadyRunning + : throw new AdbException($"The adb daemon is running an outdated version ${commandLineVersion}, but not valid path to the adb.exe executable was provided. A more recent version of the adb server cannot be started."); } if (serverStatus.IsRunning) @@ -202,8 +196,6 @@ public virtual StartServerResult StartServer(string adbPath, bool restartServerI if (serverStatus.Version < RequiredAdbVersion || (restartServerIfNewer && serverStatus.Version < commandLineVersion)) { - ExceptionExtensions.ThrowIfNull(adbPath); - StopServer(); commandLineClient.StartServer(); return StartServerResult.RestartedOutdatedDaemon; @@ -215,8 +207,6 @@ public virtual StartServerResult StartServer(string adbPath, bool restartServerI } else { - ExceptionExtensions.ThrowIfNull(adbPath); - commandLineClient.StartServer(); return StartServerResult.Started; } diff --git a/AdvancedSharpAdbClient/AdbSocket.Async.cs b/AdvancedSharpAdbClient/AdbSocket.Async.cs index a079fcbf..d775aa2c 100644 --- a/AdvancedSharpAdbClient/AdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/AdbSocket.Async.cs @@ -14,7 +14,7 @@ namespace AdvancedSharpAdbClient public partial class AdbSocket { /// - /// Reconnects the to the same endpoint it was initially connected to. + /// Asynchronously reconnects the to the same endpoint it was initially connected to. /// Use this when the socket was disconnected by adb and you have restarted adb. /// /// A which can be used to cancel the asynchronous task. @@ -354,11 +354,11 @@ public virtual async ValueTask ReadAsync(Memory data, CancellationTok #endif /// - /// Write until all data in "data" is written or the connection fails or times out. + /// Asynchronously write until all data in "data" is written or the connection fails or times out. /// /// The data to send. /// A which can be used to cancel the asynchronous task. - /// Returns if all data was written; otherwise, . + /// A which return if all data was written; otherwise, . /// This uses the default time out value. protected virtual async Task WriteAsync(byte[] data, CancellationToken cancellationToken = default) { @@ -377,11 +377,11 @@ protected virtual async Task WriteAsync(byte[] data, CancellationToken can #if HAS_BUFFERS /// - /// Write until all data in "data" is written or the connection fails or times out. + /// Asynchronously write until all data in "data" is written or the connection fails or times out. /// /// The data to send. /// A which can be used to cancel the asynchronous task. - /// Returns if all data was written; otherwise, . + /// A which return if all data was written; otherwise, . /// This uses the default time out value. protected virtual async ValueTask WriteAsync(Memory data, CancellationToken cancellationToken = default) { @@ -400,10 +400,10 @@ protected virtual async ValueTask WriteAsync(Memory data, Cancellati #endif /// - /// Reads the response from ADB after a command. + /// Asynchronously reads the response from ADB after a command. /// /// A which can be used to cancel the asynchronous task. - /// A that represents the response received from ADB. + /// A which return a that represents the response received from ADB. protected virtual async Task ReadAdbResponseInnerAsync(CancellationToken cancellationToken = default) { byte[] reply = new byte[4]; diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceClient.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceClient.Async.cs new file mode 100644 index 00000000..282632c4 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceClient.Async.cs @@ -0,0 +1,478 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; + +namespace AdvancedSharpAdbClient.DeviceCommands +{ + public partial class DeviceClient + { + /// + /// Gets the current device screen snapshot asynchronously. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which return a containing current hierarchy. + /// Failed if start with ERROR or java.lang.Exception. + public virtual async Task DumpScreenStringAsync(CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, "uiautomator dump /dev/tty", receiver, cancellationToken).ConfigureAwait(false); + + string xmlString = + receiver.ToString() + .Replace("Events injected: 1\r\n", string.Empty) + .Replace("UI hierchary dumped to: /dev/tty", string.Empty) + .Trim(); + + if (string.IsNullOrEmpty(xmlString) || xmlString.StartsWith(" + /// Gets the current device screen snapshot asynchronously. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which return a containing current hierarchy. + public virtual async Task DumpScreenAsync(CancellationToken cancellationToken = default) + { + string xmlString = await DumpScreenStringAsync(cancellationToken).ConfigureAwait(false); + XmlDocument doc = new(); + if (!string.IsNullOrEmpty(xmlString)) + { + doc.LoadXml(xmlString); + return doc; + } + return null; + } + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + /// Gets the current device screen snapshot asynchronously. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which return a containing current hierarchy. + public virtual async Task DumpScreenWinRTAsync(CancellationToken cancellationToken = default) + { + string xmlString = await DumpScreenStringAsync(cancellationToken).ConfigureAwait(false); + Windows.Data.Xml.Dom.XmlDocument doc = new(); + if (!string.IsNullOrEmpty(xmlString)) + { + doc.LoadXml(xmlString); + return doc; + } + return null; + } +#endif + + /// + /// Clicks on the specified coordinates. + /// + /// The to click. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task ClickAsync(Point cords, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input tap {cords.X} {cords.Y}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Clicks on the specified coordinates. + /// + /// The X co-ordinate to click. + /// The Y co-ordinate to click. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task ClickAsync(int x, int y, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input tap {x} {y}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Generates a swipe gesture from first element to second element. Specify the speed in ms. + /// + /// The start element. + /// The end element. + /// The time spent in swiping. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task SwipeAsync(Element first, Element second, long speed, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input swipe {first.Center.X} {first.Center.Y} {second.Center.X} {second.Center.Y} {speed}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Generates a swipe gesture from first coordinates to second coordinates. Specify the speed in ms. + /// + /// The start element. + /// The end element. + /// The time spent in swiping. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task SwipeAsync(Point first, Point second, long speed, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input swipe {first.X} {first.Y} {second.X} {second.Y} {speed}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Generates a swipe gesture from co-ordinates [x1, y1] to [x2, y2] with speed. Specify the speed in ms. + /// + /// The start X co-ordinate. + /// The start Y co-ordinate. + /// The end X co-ordinate. + /// The end Y co-ordinate. + /// The time spent in swiping. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task SwipeAsync(int x1, int y1, int x2, int y2, long speed, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input swipe {x1} {y1} {x2} {y2} {speed}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Check if the app is running in foreground. + /// + /// The package name of the app to check. + /// A which can be used to cancel the asynchronous operation. + /// A which return the result. if the app is running in foreground; otherwise, . + public virtual async Task IsAppRunningAsync(string packageName, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"pidof {packageName}", receiver, cancellationToken).ConfigureAwait(false); + + string? result = receiver.ToString().Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + bool intParsed = int.TryParse(result, out int pid); + return intParsed && pid > 0; + } + + /// + /// Check if the app is running in background. + /// + /// The package name of the app to check. + /// A which can be used to cancel the asynchronous operation. + /// A which return the result. if the app is running in background; otherwise, . + public virtual async Task IsAppInForegroundAsync(string packageName, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"dumpsys activity activities | grep mResumedActivity", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString(); + return result.Contains(packageName); + } + + /// + /// Get the of the app. + /// + /// The package name of the app to check. + /// A which can be used to cancel the asynchronous operation. + /// A which return the of the app. Foreground, stopped or running in background. + public virtual async Task GetAppStatusAsync(string packageName, CancellationToken cancellationToken = default) + { + // Check if the app is in foreground + bool currentApp = await IsAppInForegroundAsync(packageName, cancellationToken).ConfigureAwait(false); + if (currentApp) + { + return AppStatus.Foreground; + } + + // Check if the app is running in background + bool isAppRunning = await IsAppRunningAsync(packageName, cancellationToken).ConfigureAwait(false); + return isAppRunning ? AppStatus.Background : AppStatus.Stopped; + } + + /// + /// Get element by xpath asynchronously. You can specify the waiting time in timeout. + /// + /// The xpath of the elements. + /// A which can be used to cancel the asynchronous operation. + /// Only check once if . Or it will continue check until is . + /// A which return the of . + public virtual async Task FindElementAsync(string xpath = "hierarchy/node", CancellationToken cancellationToken = default) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + XmlDocument? doc = await DumpScreenAsync(cancellationToken).ConfigureAwait(false); + if (doc != null) + { + XmlNode? xmlNode = doc.SelectSingleNode(xpath); + if (xmlNode != null) + { + Element? element = Element.FromXmlNode(AdbClient, Device, xmlNode); + if (element != null) + { + return element; + } + } + } + } + catch (XmlException) + { + // Ignore XmlException and try again + } + if (cancellationToken == default) { break; } + } + } + catch (Exception e) + { + // If a cancellation was requested, this main loop is interrupted with an exception + // because the socket is closed. In that case, we don't need to throw a ShellCommandUnresponsiveException. + // In all other cases, something went wrong, and we want to report it to the user. + if (!cancellationToken.IsCancellationRequested) + { + throw new ShellCommandUnresponsiveException(e); + } + } + return null; + } + + /// + /// Get elements by xpath asynchronously. You can specify the waiting time in timeout. + /// + /// The xpath of the elements. + /// A which can be used to cancel the asynchronous operation. + /// Only check once if . Or it will continue check until is . + /// A which return the of has got. + public virtual async Task> FindElementsAsync(string xpath = "hierarchy/node", CancellationToken cancellationToken = default) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + try + { + XmlDocument? doc = await DumpScreenAsync(cancellationToken).ConfigureAwait(false); + if (doc != null) + { + XmlNodeList? xmlNodes = doc.SelectNodes(xpath); + if (xmlNodes != null) + { + static IEnumerable FindElements(IAdbClient client, DeviceData device, XmlNodeList xmlNodes) + { + for (int i = 0; i < xmlNodes.Count; i++) + { + Element? element = Element.FromXmlNode(client, device, xmlNodes[i]); + if (element != null) + { + yield return element; + } + } + } + return FindElements(AdbClient, Device, xmlNodes); + } + } + } + catch (XmlException) + { + // Ignore XmlException and try again + } + if (cancellationToken == default) { break; } + } + } + catch (Exception e) + { + // If a cancellation was requested, this main loop is interrupted with an exception + // because the socket is closed. In that case, we don't need to throw a ShellCommandUnresponsiveException. + // In all other cases, something went wrong, and we want to report it to the user. + if (!cancellationToken.IsCancellationRequested) + { + throw new ShellCommandUnresponsiveException(e); + } + } + return Enumerable.Empty(); + } + +#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + /// Get elements by xpath asynchronously. You can specify the waiting time in timeout. + /// + /// The xpath of the elements. + /// A which can be used to cancel the asynchronous operation. + /// Only check once if . Or it will continue check until is . + /// A which return the of has got. + public virtual async IAsyncEnumerable FindAsyncElements(string xpath = "hierarchy/node", [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + while (!cancellationToken.IsCancellationRequested) + { + XmlDocument? doc = null; + + try + { + doc = await DumpScreenAsync(cancellationToken).ConfigureAwait(false); + } + catch (XmlException) + { + // Ignore XmlException and try again + } + catch (Exception e) + { + // If a cancellation was requested, this main loop is interrupted with an exception + // because the socket is closed. In that case, we don't need to throw a ShellCommandUnresponsiveException. + // In all other cases, something went wrong, and we want to report it to the user. + if (!cancellationToken.IsCancellationRequested) + { + throw new ShellCommandUnresponsiveException(e); + } + } + + if (doc != null) + { + XmlNodeList? xmlNodes = doc.SelectNodes(xpath); + if (xmlNodes != null) + { + for (int i = 0; i < xmlNodes.Count; i++) + { + Element? element = Element.FromXmlNode(AdbClient, Device, xmlNodes[i]); + if (element != null) + { + yield return element; + } + } + break; + } + } + + if (cancellationToken == default) { break; } + } + } +#endif + + /// + /// Send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. + /// + /// The key event to send. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task SendKeyEventAsync(string key, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input keyevent {key}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new InvalidKeyEventException("KeyEvent is invalid"); + } + } + + /// + /// Send text to device. Doesn't support Russian. + /// + /// The text to send. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual async Task SendTextAsync(string text, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + await AdbClient.ExecuteShellCommandAsync(Device, $"input text {text}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new InvalidTextException(); + } + } + + /// + /// Start an Android application on device. + /// + /// The package name of the application to start. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual Task StartAppAsync(string packageName, CancellationToken cancellationToken = default) => AdbClient.ExecuteShellCommandAsync(Device, $"monkey -p {packageName} 1", cancellationToken); + + /// + /// Stop an Android application on device. + /// + /// The package name of the application to stop. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public virtual Task StopAppAsync(string packageName, CancellationToken cancellationToken = default) => AdbClient.ExecuteShellCommandAsync(Device, $"am force-stop {packageName}", cancellationToken); + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceClient.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceClient.cs new file mode 100644 index 00000000..d5963db5 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceClient.cs @@ -0,0 +1,425 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; + +namespace AdvancedSharpAdbClient.DeviceCommands +{ + /// + /// A class which contains methods for interacting with an Android device by + /// + public partial class DeviceClient + { + /// + /// Initializes a new instance of the class. + /// + /// The to use to communicate with the Android Debug Bridge. + /// The device on which to process command. + public DeviceClient(IAdbClient client, DeviceData device) + { + ExceptionExtensions.ThrowIfNull(client); + ExceptionExtensions.ThrowIfNull(device); + if (string.IsNullOrEmpty(device.Serial)) + { + throw new ArgumentOutOfRangeException(nameof(device), "You must specific a serial number for the device"); + } + Device = device; + AdbClient = client; + } + + /// + /// Gets the device. + /// + public DeviceData Device { get; init; } + + /// + /// The to use when communicating with the device. + /// + public IAdbClient AdbClient { get; init; } + + /// + /// Gets the current device screen snapshot. + /// + /// A containing current hierarchy. + /// Failed if start with ERROR or java.lang.Exception. + public virtual string DumpScreenString() + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, "uiautomator dump /dev/tty", receiver); + + string xmlString = + receiver.ToString() + .Replace("Events injected: 1\r\n", string.Empty) + .Replace("UI hierchary dumped to: /dev/tty", string.Empty) + .Trim(); + + if (string.IsNullOrEmpty(xmlString) || xmlString.StartsWith(" + /// Gets the current device screen snapshot. + /// + /// A containing current hierarchy. + public virtual XmlDocument? DumpScreen() + { + XmlDocument doc = new(); + string xmlString = DumpScreenString(); + if (!string.IsNullOrEmpty(xmlString)) + { + doc.LoadXml(xmlString); + return doc; + } + return null; + } + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + /// Gets the current device screen snapshot. + /// + /// A containing current hierarchy. + public virtual Windows.Data.Xml.Dom.XmlDocument? DumpScreenWinRT() + { + Windows.Data.Xml.Dom.XmlDocument doc = new(); + string xmlString = DumpScreenString(); + if (!string.IsNullOrEmpty(xmlString)) + { + doc.LoadXml(xmlString); + return doc; + } + return null; + } +#endif + + /// + /// Clicks on the specified coordinates. + /// + /// The to click. + public virtual void Click(Point cords) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input tap {cords.X} {cords.Y}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Clicks on the specified coordinates. + /// + /// The X co-ordinate to click. + /// The Y co-ordinate to click. + public virtual void Click(int x, int y) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input tap {x} {y}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Generates a swipe gesture from first element to second element. Specify the speed in ms. + /// + /// The start element. + /// The end element. + /// The time spent in swiping. + public virtual void Swipe(Element first, Element second, long speed) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input swipe {first.Center.X} {first.Center.Y} {second.Center.X} {second.Center.Y} {speed}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Generates a swipe gesture from first coordinates to second coordinates. Specify the speed in ms. + /// + /// The start . + /// The end . + /// The time spent in swiping. + public virtual void Swipe(Point first, Point second, long speed) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input swipe {first.X} {first.Y} {second.X} {second.Y} {speed}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Generates a swipe gesture from co-ordinates [x1, y1] to [x2, y2] with speed. Specify the speed in ms. + /// + /// The start X co-ordinate. + /// The start Y co-ordinate. + /// The end X co-ordinate. + /// The end Y co-ordinate. + /// The time spent in swiping. + public virtual void Swipe(int x1, int y1, int x2, int y2, long speed) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input swipe {x1} {y1} {x2} {y2} {speed}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } + + /// + /// Check if the app is running in foreground. + /// + /// The package name of the app to check. + /// if the app is running in foreground; otherwise, . + public virtual bool IsAppRunning(string packageName) + { + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"pidof {packageName}", receiver); + + string? result = receiver.ToString().Split(' ', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); + bool intParsed = int.TryParse(result, out int pid); + return intParsed && pid > 0; + } + + /// + /// Check if the app is running in background. + /// + /// The package name of the app to check. + /// if the app is running in background; otherwise, . + public virtual bool IsAppInForeground(string packageName) + { + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"dumpsys activity activities | grep mResumedActivity", receiver); + + string result = receiver.ToString(); + return result.Contains(packageName); + } + + /// + /// Get the of the app. + /// + /// The package name of the app to check. + /// The of the app. Foreground, stopped or running in background. + public virtual AppStatus GetAppStatus(string packageName) + { + // Check if the app is in foreground + bool currentApp = IsAppInForeground(packageName); + if (currentApp) + { + return AppStatus.Foreground; + } + + // Check if the app is running in background + bool isAppRunning = IsAppRunning(packageName); + return isAppRunning ? AppStatus.Background : AppStatus.Stopped; + } + + /// + /// Get element by xpath. You can specify the waiting time in timeout. + /// + /// The xpath of the element. + /// The timeout for waiting the element. + /// Only check once if or . + /// The of . + public virtual Element? FindElement(string xpath = "hierarchy/node", TimeSpan timeout = default) + { + Stopwatch stopwatch = new(); + stopwatch.Start(); + do + { + try + { + XmlDocument? doc = DumpScreen(); + if (doc != null) + { + XmlNode? xmlNode = doc.SelectSingleNode(xpath); + if (xmlNode != null) + { + Element? element = Element.FromXmlNode(AdbClient, Device, xmlNode); + if (element != null) + { + return element; + } + } + } + } + catch (XmlException) + { + // Ignore XmlException and try again + } + if (timeout == default) { break; } + } + while (stopwatch.Elapsed < timeout); + return null; + } + + /// + /// Get elements by xpath. You can specify the waiting time in timeout. + /// + /// The xpath of the elements. + /// The timeout for waiting the elements. + /// Only check once if or . + /// The of has got. + public virtual IEnumerable FindElements(string xpath = "hierarchy/node", TimeSpan timeout = default) + { + Stopwatch stopwatch = new(); + stopwatch.Start(); + do + { + XmlDocument? doc = null; + + try + { + doc = DumpScreen(); + } + catch (XmlException) + { + // Ignore XmlException and try again + } + + if (doc != null) + { + XmlNodeList? xmlNodes = doc.SelectNodes(xpath); + if (xmlNodes != null) + { + for (int i = 0; i < xmlNodes.Count; i++) + { + Element? element = Element.FromXmlNode(AdbClient, Device, xmlNodes[i]); + if (element != null) + { + yield return element; + } + } + break; + } + } + + if (timeout == default) { break; } + } + while (stopwatch.Elapsed < timeout); + } + + /// + /// Send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. + /// + /// The key event to send. + public virtual void SendKeyEvent(string key) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input keyevent {key}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new InvalidKeyEventException("KeyEvent is invalid"); + } + } + + /// + /// Send text to device. Doesn't support Russian. + /// + /// The text to send. + public virtual void SendText(string text) + { + ConsoleOutputReceiver receiver = new() { ParsesErrors = false }; + AdbClient.ExecuteShellCommand(Device, $"input text {text}", receiver); + + string result = receiver.ToString().Trim(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new InvalidTextException(); + } + } + + /// + /// Start an Android application on device. + /// + /// The package name of the application to start. + public virtual void StartApp(string packageName) => AdbClient.ExecuteShellCommand(Device, $"monkey -p {packageName} 1"); + + /// + /// Stop an Android application on device. + /// + /// The package name of the application to stop. + public virtual void StopApp(string packageName) => AdbClient.ExecuteShellCommand(Device, $"am force-stop {packageName}"); + + /// + /// Deconstruct the class. + /// + /// The to use to communicate with the Android Debug Bridge. + /// The device on which to process command. + public virtual void Deconstruct(out IAdbClient client, out DeviceData device) + { + client = AdbClient; + device = Device; + } + +#if NET7_0_OR_GREATER + [GeneratedRegex("<\\?xml(.?)*")] + private static partial Regex GetXmlRegex(); +#else + private static Regex GetXmlRegex() => new("<\\?xml(.?)*"); +#endif + } +} diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs index c4fa69d2..3516016d 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs @@ -9,13 +9,14 @@ using System.Linq; using System.Text; using System.Threading; +using System.Xml; namespace AdvancedSharpAdbClient.DeviceCommands { public static partial class DeviceExtensions { /// - /// Executes a shell command on the device. + /// Asynchronously executes a shell command on the device. /// /// The to use when executing the command. /// The device on which to run the command. @@ -26,7 +27,7 @@ public static Task ExecuteShellCommandAsync(this IAdbClient client, DeviceData d client.ExecuteRemoteCommandAsync(command, device, AdbClient.Encoding, cancellationToken); /// - /// Executes a shell command on the device. + /// Asynchronously executes a shell command on the device. /// /// The to use when executing the command. /// The device on which to run the command. @@ -38,7 +39,155 @@ public static Task ExecuteShellCommandAsync(this IAdbClient client, DeviceData d client.ExecuteRemoteCommandAsync(command, device, receiver, AdbClient.Encoding, cancellationToken); /// - /// Gets the file statistics of a given file. + /// Asynchronously gets the current device screen snapshot asynchronously. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// A which can be used to cancel the asynchronous operation. + /// A which return a containing current hierarchy. + public static Task DumpScreenAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).DumpScreenAsync(cancellationToken); + + /// + /// Asynchronously clicks on the specified coordinates. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The to click. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task ClickAsync(this IAdbClient client, DeviceData device, Point cords, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).ClickAsync(cords, cancellationToken); + + /// + /// Asynchronously generates a swipe gesture from first coordinates to second coordinates. Specify the speed in ms. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The start element. + /// The end element. + /// The time spent in swiping. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task SwipeAsync(this IAdbClient client, DeviceData device, Point first, Point second, long speed, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).SwipeAsync(first, second, speed, cancellationToken); + + /// + /// Asynchronously get the of the app. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The package name of the app to check. + /// A which can be used to cancel the asynchronous operation. + /// A which return the of the app. Foreground, stopped or running in background. + public static Task GetAppStatusAsync(this IAdbClient client, DeviceData device, string packageName, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).GetAppStatusAsync(packageName, cancellationToken); + + /// + /// Asynchronously get element by xpath asynchronously. You can specify the waiting time in timeout. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The xpath of the elements. + /// A which can be used to cancel the asynchronous operation. + /// Only check once if . Or it will continue check until is . + /// A which return the of . + public static Task FindElementAsync(this IAdbClient client, DeviceData device, string xpath = "hierarchy/node", CancellationToken cancellationToken = default) => + new DeviceClient(client, device).FindElementAsync(xpath, cancellationToken); + + /// + /// Asynchronously get elements by xpath asynchronously. You can specify the waiting time in timeout. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The xpath of the elements. + /// A which can be used to cancel the asynchronous operation. + /// Only check once if . Or it will continue check until is . + /// A which return the of has got. + public static Task> FindElementsAsync(this IAdbClient client, DeviceData device, string xpath = "hierarchy/node", CancellationToken cancellationToken = default) => + new DeviceClient(client, device).FindElementsAsync(xpath, cancellationToken); + + /// + /// Asynchronously send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The key event to send. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task SendKeyEventAsync(this IAdbClient client, DeviceData device, string key, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).SendKeyEventAsync(key, cancellationToken); + + /// + /// Asynchronously send text to device. Doesn't support Russian. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The text to send. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task SendTextAsync(this IAdbClient client, DeviceData device, string text, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).SendTextAsync(text, cancellationToken); + + /// + /// Asynchronously clear the input text. The input should be in focus. Use if the element isn't focused. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The length of text to clear. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static async Task ClearInputAsync(this IAdbClient client, DeviceData device, int charCount, CancellationToken cancellationToken = default) + { + DeviceClient deviceClient = new(client, device); + await deviceClient.SendKeyEventAsync("KEYCODE_MOVE_END", cancellationToken).ConfigureAwait(false); + await deviceClient.SendKeyEventAsync(StringExtensions.Join(" ", Enumerable.Repeat("KEYCODE_DEL", charCount)), cancellationToken).ConfigureAwait(false); + } + + /// + /// Asynchronously click BACK button. + /// + /// An instance of a class that implements the interface. + /// The device on which to click BACK button. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task ClickBackButtonAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).SendKeyEventAsync("KEYCODE_BACK", cancellationToken); + + /// + /// Asynchronously click HOME button. + /// + /// An instance of a class that implements the interface. + /// The device on which to click HOME button. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task ClickHomeButtonAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) => + new DeviceClient(client, device).SendKeyEventAsync("KEYCODE_HOME", cancellationToken); + + /// + /// Asynchronously start an Android application on device. + /// + /// An instance of a class that implements the interface. + /// The device on which to click HOME button. + /// The package name of the application to start. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task StartAppAsync(this IAdbClient client, DeviceData device, string packageName, CancellationToken cancellationToken = default) => + client.ExecuteShellCommandAsync(device, $"monkey -p {packageName} 1", cancellationToken); + + /// + /// Asynchronously stop an Android application on device. + /// + /// An instance of a class that implements the interface. + /// The device on which to click HOME button. + /// The package name of the application to stop. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task StopAppAsync(this IAdbClient client, DeviceData device, string packageName, CancellationToken cancellationToken = default) => + client.ExecuteShellCommandAsync(device, $"am force-stop {packageName}", cancellationToken); + + /// + /// Asynchronously gets the file statistics of a given file. /// /// The to use when executing the command. /// The device on which to look for the file. @@ -52,7 +201,7 @@ public static async Task StatAsync(this IAdbClient client, Devic } /// - /// Lists the contents of a directory on the device. + /// Asynchronously lists the contents of a directory on the device. /// /// The to use when executing the command. /// The device on which to list the directory. @@ -66,7 +215,7 @@ public static async Task> ListAsync(this IAdbClient client, } /// - /// Pulls (downloads) a file from the remote device. + /// Asynchronously pulls (downloads) a file from the remote device. /// /// The to use when executing the command. /// The device on which to pull the file. @@ -84,7 +233,7 @@ public static async Task PullAsync(this IAdbClient client, DeviceData device, } /// - /// Pushes (uploads) a file to the remote device. + /// Asynchronously pushes (uploads) a file to the remote device. /// /// The to use when executing the command. /// The device on which to put the file. @@ -104,7 +253,7 @@ public static async Task PushAsync(this IAdbClient client, DeviceData device, } /// - /// Gets the property of a device. + /// Asynchronously gets the property of a device. /// /// The connection to the adb server. /// The device for which to get the property. @@ -119,7 +268,7 @@ public static async Task GetPropertyAsync(this IAdbClient client, Device } /// - /// Gets the properties of a device. + /// Asynchronously gets the properties of a device. /// /// The connection to the adb server. /// The device for which to list the properties. @@ -133,7 +282,7 @@ public static async Task> GetPropertiesAsync(this IAd } /// - /// Gets the environment variables currently defined on a device. + /// Asynchronously gets the environment variables currently defined on a device. /// /// The connection to the adb server. /// The device for which to list the environment variables. @@ -147,7 +296,60 @@ public static async Task> GetEnvironmentVariablesAsyn } /// - /// Uninstalls a package from the device. + /// Asynchronously installs an Android application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute file system path to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install. + /// A which represents the asynchronous operation. + public static Task InstallPackageAsync(this IAdbClient client, DeviceData device, string packageFilePath, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.InstallPackageAsync(packageFilePath, progress, cancellationToken, arguments); + } + + /// + /// Asynchronously installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute base app file system path to file on local host to install. + /// The absolute split app file system paths to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceData device, string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.InstallMultiplePackageAsync(basePackageFilePath, splitPackageFilePaths, progress, cancellationToken, arguments); + } + + /// + /// Asynchronously installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute split app file system paths to file on local host to install. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceData device, IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.InstallMultiplePackageAsync(splitPackageFilePaths, packageName, progress, cancellationToken, arguments); + } + + /// + /// Asynchronously uninstalls a package from the device. /// /// The connection to the adb server. /// The device on which to uninstall the package. @@ -156,12 +358,27 @@ public static async Task> GetEnvironmentVariablesAsyn /// A which represents the asynchronous operation. public static Task UninstallPackageAsync(this IAdbClient client, DeviceData device, string packageName, CancellationToken cancellationToken = default) { - PackageManager manager = new(client, device); + PackageManager manager = new(client, device, skipInit: true); return manager.UninstallPackageAsync(packageName, cancellationToken); } /// - /// Requests the version information from the device. + /// Asynchronously uninstalls a package from the device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The name of the package to uninstall. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm uninstall. + /// A which represents the asynchronous operation. + public static Task UninstallPackageAsync(this IAdbClient client, DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.UninstallPackageAsync(packageName, cancellationToken, arguments); + } + + /// + /// Asynchronously requests the version information from the device. /// /// The connection to the adb server. /// The device on which to uninstall the package. @@ -170,12 +387,12 @@ public static Task UninstallPackageAsync(this IAdbClient client, DeviceData devi /// A which return the of target application. public static Task GetPackageVersionAsync(this IAdbClient client, DeviceData device, string packageName, CancellationToken cancellationToken = default) { - PackageManager manager = new(client, device); + PackageManager manager = new(client, device, skipInit: true); return manager.GetVersionInfoAsync(packageName, cancellationToken); } /// - /// Lists all processes running on the device. + /// Asynchronously lists all processes running on the device. /// /// A connection to ADB. /// The device on which to list the processes that are running. diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs index b3e0f4fb..a0dac92a 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Xml; namespace AdvancedSharpAdbClient.DeviceCommands { @@ -35,6 +36,134 @@ public static void ExecuteShellCommand(this IAdbClient client, DeviceData device public static void ExecuteShellCommand(this IAdbClient client, DeviceData device, string command, IShellOutputReceiver receiver) => client.ExecuteRemoteCommand(command, device, receiver, AdbClient.Encoding); + /// + /// Gets the current device screen snapshot. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// A containing current hierarchy. + public static XmlDocument? DumpScreen(this IAdbClient client, DeviceData device) => + new DeviceClient(client, device).DumpScreen(); + + /// + /// Clicks on the specified coordinates. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The to click. + public static void Click(this IAdbClient client, DeviceData device, Point cords) => + new DeviceClient(client, device).Click(cords); + + /// + /// Generates a swipe gesture from first coordinates to second coordinates. Specify the speed in ms. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The start . + /// The end . + /// The time spent in swiping. + public static void Swipe(this IAdbClient client, DeviceData device, Point first, Point second, long speed) => + new DeviceClient(client, device).Swipe(first, second, speed); + + /// + /// Get the of the app. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The package name of the app to check. + /// The of the app. Foreground, stopped or running in background. + public static AppStatus GetAppStatus(this IAdbClient client, DeviceData device, string packageName) => + new DeviceClient(client, device).GetAppStatus(packageName); + + /// + /// Get element by xpath. You can specify the waiting time in timeout. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The xpath of the element. + /// The timeout for waiting the element. + /// Only check once if or . + /// The of . + public static Element? FindElement(this IAdbClient client, DeviceData device, string xpath = "hierarchy/node", TimeSpan timeout = default) => + new DeviceClient(client, device).FindElement(xpath, timeout); + + /// + /// Get elements by xpath. You can specify the waiting time in timeout. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The xpath of the elements. + /// The timeout for waiting the elements. + /// Only check once if or . + /// The of has got. + public static IEnumerable FindElements(this IAdbClient client, DeviceData device, string xpath = "hierarchy/node", TimeSpan timeout = default) => + new DeviceClient(client, device).FindElements(xpath, timeout); + + /// + /// Send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The key event to send. + public static void SendKeyEvent(this IAdbClient client, DeviceData device, string key) => + new DeviceClient(client, device).SendKeyEvent(key); + + /// + /// Send text to device. Doesn't support Russian. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The text to send. + public static void SendText(this IAdbClient client, DeviceData device, string text) => + new DeviceClient(client, device).SendText(text); + + /// + /// Clear the input text. The input should be in focus. Use if the element isn't focused. + /// + /// An instance of a class that implements the interface. + /// The device on which to clear the input text. + /// The length of text to clear. + public static void ClearInput(this IAdbClient client, DeviceData device, int charCount) + { + DeviceClient deviceClient = new(client, device); + deviceClient.SendKeyEvent("KEYCODE_MOVE_END"); + deviceClient.SendKeyEvent(StringExtensions.Join(" ", Enumerable.Repeat("KEYCODE_DEL", charCount))); + } + + /// + /// Click BACK button. + /// + /// An instance of a class that implements the interface. + /// The device on which to click BACK button. + public static void ClickBackButton(this IAdbClient client, DeviceData device) => + new DeviceClient(client, device).SendKeyEvent("KEYCODE_BACK"); + + /// + /// Click HOME button. + /// + /// An instance of a class that implements the interface. + /// The device on which to click HOME button. + public static void ClickHomeButton(this IAdbClient client, DeviceData device) => + new DeviceClient(client, device).SendKeyEvent("KEYCODE_HOME"); + + /// + /// Start an Android application on device. + /// + /// An instance of a class that implements the interface. + /// The device on which to click HOME button. + /// The package name of the application to start. + public static void StartApp(this IAdbClient client, DeviceData device, string packageName) => + client.ExecuteShellCommand(device, $"monkey -p {packageName} 1"); + + /// + /// Stop an Android application on device. + /// + /// An instance of a class that implements the interface. + /// The device on which to click HOME button. + /// The package name of the application to stop. + public static void StopApp(this IAdbClient client, DeviceData device, string packageName) => + client.ExecuteShellCommand(device, $"am force-stop {packageName}"); + /// /// Gets the file statistics of a given file. /// @@ -142,16 +271,64 @@ public static Dictionary GetEnvironmentVariables(this IAdbClient return receiver.EnvironmentVariables; } + /// + /// Installs an Android application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute file system path to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install. + public static void InstallPackage(this IAdbClient client, DeviceData device, string packageFilePath, IProgress? progress = null, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + manager.InstallPackage(packageFilePath, progress, arguments); + } + + /// + /// Installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute base app file system path to file on local host to install. + /// The absolute split app file system paths to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public static void InstallMultiplePackage(this IAdbClient client, DeviceData device, string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + manager.InstallMultiplePackage(basePackageFilePath, splitPackageFilePaths, progress, arguments); + } + + /// + /// Installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute split app file system paths to file on local host to install. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public static void InstallMultiplePackage(this IAdbClient client, DeviceData device, IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + manager.InstallMultiplePackage(splitPackageFilePaths, packageName, progress, arguments); + } + /// /// Uninstalls a package from the device. /// /// The connection to the adb server. /// The device on which to uninstall the package. /// The name of the package to uninstall. - public static void UninstallPackage(this IAdbClient client, DeviceData device, string packageName) + /// The arguments to pass to pm uninstall. + public static void UninstallPackage(this IAdbClient client, DeviceData device, string packageName, params string[] arguments) { - PackageManager manager = new(client, device); - manager.UninstallPackage(packageName); + PackageManager manager = new(client, device, skipInit: true); + manager.UninstallPackage(packageName, arguments); } /// @@ -162,7 +339,7 @@ public static void UninstallPackage(this IAdbClient client, DeviceData device, s /// The name of the package from which to get the application version. public static VersionInfo GetPackageVersion(this IAdbClient client, DeviceData device, string packageName) { - PackageManager manager = new(client, device); + PackageManager manager = new(client, device, skipInit: true); return manager.GetVersionInfo(packageName); } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/AndroidProcess.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/AndroidProcess.cs index 408a5f4d..107c105b 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/AndroidProcess.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/AndroidProcess.cs @@ -4,7 +4,7 @@ using System; -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Models { /// /// Represents a process running on an Android device. diff --git a/AdvancedSharpAdbClient/Models/Element.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/Element.cs similarity index 82% rename from AdvancedSharpAdbClient/Models/Element.cs rename to AdvancedSharpAdbClient/DeviceCommands/Models/Element.cs index 454a89a5..532e4142 100644 --- a/AdvancedSharpAdbClient/Models/Element.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/Element.cs @@ -9,7 +9,7 @@ using System.Threading; using System.Xml; -namespace AdvancedSharpAdbClient.Models +namespace AdvancedSharpAdbClient.DeviceCommands.Models { /// /// Implement of screen element, likes Selenium. @@ -222,7 +222,22 @@ static IEnumerable FindElements(IAdbClient client, DeviceData device, W /// /// Clicks on this coordinates. /// - public void Click() => Client.Click(Device, Center); + public void Click() + { + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + Client.ExecuteShellCommand(Device, $"input tap {Center.X} {Center.Y}", receiver); + + string result = receiver.ToString(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } /// /// Send text to device. Doesn't support Russian. @@ -231,17 +246,30 @@ static IEnumerable FindElements(IAdbClient client, DeviceData device, W public void SendText(string text) { Click(); - Client.SendText(Device, text); + + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + Client.ExecuteShellCommand(Device, $"input text {text}", receiver); + + string result = receiver.ToString(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new InvalidTextException(); + } } /// - /// Clear the input text. Use if the element is focused. + /// Clear the input text. Use if the element is focused. /// [MemberNotNull(nameof(Text))] public void ClearInput() => ClearInput(Text!.Length); /// - /// Clear the input text. Use if the element is focused. + /// Clear the input text. Use if the element is focused. /// /// The length of text to clear. public void ClearInput(int charCount) @@ -252,14 +280,28 @@ public void ClearInput(int charCount) #if HAS_TASK /// - /// Clicks on this coordinates. + /// Asynchronously clicks on this coordinates. /// /// A which can be used to cancel the asynchronous operation. - public Task ClickAsync(CancellationToken cancellationToken = default) => - Client.ClickAsync(Device, Center, cancellationToken); + public async Task ClickAsync(CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + await Client.ExecuteShellCommandAsync(Device, $"input tap {Center.X} {Center.Y}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new ElementNotFoundException("Coordinates of element is invalid"); + } + } /// - /// Send text to device. Doesn't support Russian. + /// Asynchronously send text to device. Doesn't support Russian. /// /// The text to send. /// A which can be used to cancel the asynchronous operation. @@ -267,11 +309,24 @@ public Task ClickAsync(CancellationToken cancellationToken = default) => public async Task SendTextAsync(string text, CancellationToken cancellationToken = default) { await ClickAsync(cancellationToken).ConfigureAwait(false); - await Client.SendTextAsync(Device, text, cancellationToken).ConfigureAwait(false); + + ConsoleOutputReceiver receiver = new() { TrimLines = true, ParsesErrors = false }; + await Client.ExecuteShellCommandAsync(Device, $"input text {text}", receiver, cancellationToken).ConfigureAwait(false); + + string result = receiver.ToString(); + + if (result.StartsWith("java.lang.")) + { + throw JavaException.Parse(result); + } + else if (result.Contains("ERROR", StringComparison.OrdinalIgnoreCase)) // error or ERROR + { + throw new InvalidTextException(); + } } /// - /// Clear the input text. Use if the element is focused. + /// Asynchronously clear the input text. Use if the element is focused. /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. @@ -280,7 +335,7 @@ public Task ClearInputAsync(CancellationToken cancellationToken = default) => ClearInputAsync(Text!.Length, cancellationToken); /// - /// Clear the input text. Use if the element is focused. + /// Asynchronously clear the input text. Use if the element is focused. /// /// The length of text to clear. /// A which can be used to cancel the asynchronous operation. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/AndroidProcessState.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/AndroidProcessState.cs index 671963bb..1b7d224f 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/AndroidProcessState.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/AndroidProcessState.cs @@ -2,7 +2,7 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Models { /// /// Represents the state of a process running on an Android device. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PerProcessFlags.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PerProcessFlags.cs index d93bc175..225d9941 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PerProcessFlags.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PerProcessFlags.cs @@ -4,7 +4,7 @@ using System; -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Models { /// /// Per process flags. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/NamespaceDoc.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/NamespaceDoc.cs index 91d43186..644e8e94 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/NamespaceDoc.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/NamespaceDoc.cs @@ -5,13 +5,13 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Models { /// - /// The classes in this namespace provide models for . + /// The classes in this namespace provide models for . /// /// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. [CompilerGenerated] [EditorBrowsable(EditorBrowsableState.Never)] - internal class NamespaceDoc : Models.NamespaceDoc { } + internal class NamespaceDoc : AdvancedSharpAdbClient.Models.NamespaceDoc { } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs index a52af84b..61ad2c8e 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs @@ -7,7 +7,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Models { /// /// Represents a version of an Android application. diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs index 1f157221..3a9abb57 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs @@ -15,7 +15,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands public partial class PackageManager { /// - /// Refreshes the packages. + /// Asynchronously refreshes the packages. /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. @@ -39,15 +39,18 @@ public virtual Task RefreshPackagesAsync(CancellationToken cancellationToken = d } /// - /// Installs an Android application on device. + /// Asynchronously installs an Android application on device. /// /// The absolute file system path to file on local host to install. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install. /// A which represents the asynchronous operation. public virtual async Task InstallPackageAsync(string packageFilePath, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + ValidateDevice(); void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => @@ -65,10 +68,11 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => } /// - /// Installs the application package that was pushed to a temporary location on the device. + /// Asynchronously installs the application package that was pushed to a temporary location on the device. /// /// absolute file path to package file on device. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install. /// A which represents the asynchronous operation. @@ -101,16 +105,19 @@ public virtual async Task InstallRemotePackageAsync(string remoteFilePath, IProg } /// - /// Installs Android multiple application on device. + /// Asynchronously installs Android multiple application on device. /// /// The absolute base app file system path to file on local host to install. /// The absolute split app file system paths to file on local host to install. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. public virtual async Task InstallMultiplePackageAsync(string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + ValidateDevice(); int splitPackageFileCount = splitPackageFilePaths.Count(); @@ -174,16 +181,19 @@ await Extensions.WhenAll(splitRemoteFilePaths.Select(async x => } /// - /// Installs Android multiple application on device. + /// Asynchronously installs Android multiple application on device. /// /// The absolute split app file system paths to file on local host to install. /// The absolute package name of the base app. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. public virtual async Task InstallMultiplePackageAsync(IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + ValidateDevice(); int splitPackageFileCount = splitPackageFilePaths.Count(); @@ -239,11 +249,12 @@ await Extensions.WhenAll(splitRemoteFilePaths.Select(async x => } /// - /// Installs the multiple application package that was pushed to a temporary location on the device. + /// Asynchronously installs the multiple application package that was pushed to a temporary location on the device. /// /// The absolute base app file path to package file on device. /// The absolute split app file paths to package file on device. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. @@ -287,11 +298,12 @@ await Extensions.WhenAll(splitRemoteFilePaths.Select(async (splitRemoteFilePath) } /// - /// Installs the multiple application package that was pushed to a temporary location on the device. + /// Asynchronously installs the multiple application package that was pushed to a temporary location on the device. /// /// The absolute split app file paths to package file on device. /// The absolute package name of the base app. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. @@ -331,7 +343,7 @@ await Extensions.WhenAll(splitRemoteFilePaths.Select(async (splitRemoteFilePath) } /// - /// Uninstalls a package from the device. + /// Asynchronously uninstalls a package from the device. /// /// The name of the package to uninstall. /// The arguments to pass to pm uninstall. @@ -340,7 +352,7 @@ public Task UninstallPackageAsync(string packageName, params string[] arguments) UninstallPackageAsync(packageName, default, arguments); /// - /// Uninstalls a package from the device. + /// Asynchronously uninstalls a package from the device. /// /// The name of the package to uninstall. /// A which can be used to cancel the asynchronous operation. @@ -372,7 +384,7 @@ public virtual async Task UninstallPackageAsync(string packageName, Cancellation } /// - /// Requests the version information from the device. + /// Asynchronously requests the version information from the device. /// /// The name of the package from which to get the application version. /// A which can be used to cancel the asynchronous operation. @@ -387,7 +399,20 @@ public virtual async Task GetVersionInfoAsync(string packageName, C } /// - /// Pushes a file to device + /// Asynchronously opens an existing file for reading. + /// + /// The file to be opened for reading. + /// A which can be used to cancel the asynchronous operation. + /// A read-only on the specified path. + protected virtual Task GetFileStreamAsync(string path, CancellationToken cancellationToken = default) => +#if WINDOWS_UWP + StorageFile.GetFileFromPathAsync(path).AsTask(cancellationToken).ContinueWith(x => x.Result.OpenReadAsync().AsTask(cancellationToken)).Unwrap().ContinueWith(x => x.Result.AsStream()); +#else + Extensions.FromResult(File.OpenRead(path)); +#endif + + /// + /// Asynchronously pushes a file to device /// /// The absolute path to file on local host. /// An optional parameter which, when specified, returns progress notifications. @@ -416,7 +441,7 @@ protected virtual async Task SyncPackageToDeviceAsync(string localFilePa #if NETCOREAPP3_0_OR_GREATER await #endif - using FileStream stream = File.OpenRead(localFilePath); + using Stream stream = await GetFileStreamAsync(localFilePath, cancellationToken).ConfigureAwait(false); logger.LogDebug("Uploading file onto device '{0}'", Device.Serial); @@ -440,7 +465,7 @@ protected virtual async Task SyncPackageToDeviceAsync(string localFilePa } /// - /// Remove a file from device. + /// Asynchronously remove a file from device. /// /// Path on device of file to remove. /// A which can be used to cancel the asynchronous operation. @@ -461,7 +486,7 @@ protected virtual async Task RemoveRemotePackageAsync(string remoteFilePath, Can } /// - /// Like "install", but starts an install session. + /// Like "install", but starts an install session asynchronously. /// /// The absolute package name of the base app. /// A which can be used to cancel the asynchronous operation. @@ -503,7 +528,7 @@ protected virtual async Task CreateInstallSessionAsync(string? packageNa } /// - /// Write an apk into the given install session. + /// Asynchronously write an apk into the given install session. /// /// The session ID of the install session. /// The name of the application. diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs index 09c7aac8..70db1c20 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Text; @@ -110,7 +111,7 @@ public PackageManager(IAdbClient client, DeviceData device, Func /// The to use when communicating with the device. /// - protected IAdbClient AdbClient { get; init; } + public IAdbClient AdbClient { get; init; } /// /// Refreshes the packages. @@ -138,10 +139,13 @@ public virtual void RefreshPackages() /// Installs an Android application on device. /// /// The absolute file system path to file on local host to install. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install. public virtual void InstallPackage(string packageFilePath, IProgress? progress = null, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + ValidateDevice(); void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => @@ -162,7 +166,8 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => /// Installs the application package that was pushed to a temporary location on the device. /// /// absolute file path to package file on device. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install. public virtual void InstallRemotePackage(string remoteFilePath, IProgress? progress = null, params string[] arguments) { @@ -197,10 +202,13 @@ public virtual void InstallRemotePackage(string remoteFilePath, IProgress /// The absolute base app file system path to file on local host to install. /// The absolute split app file system paths to file on local host to install. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. public virtual void InstallMultiplePackage(string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + ValidateDevice(); int splitPackageFileCount = splitPackageFilePaths.Count(); @@ -256,10 +264,13 @@ void OnSplitSyncProgressChanged(string? sender, SyncProgressChangedEventArgs arg /// /// The absolute split app file system paths to file on local host to install. /// The absolute package name of the base app. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. public virtual void InstallMultiplePackage(IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, params string[] arguments) { + progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + ValidateDevice(); int splitPackageFileCount = splitPackageFilePaths.Count(); @@ -307,7 +318,8 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) /// /// The absolute base app file path to package file on device. /// The absolute split app file paths to package file on device. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, IProgress? progress = null, params string[] arguments) { @@ -348,7 +360,8 @@ public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnu /// /// The absolute split app file paths to package file on device. /// The absolute package name of the base app. - /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of installation. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. public virtual void InstallMultipleRemotePackage(IEnumerable splitRemoteFilePaths, string packageName, IProgress? progress = null, params string[] arguments) { @@ -435,6 +448,18 @@ protected void ValidateDevice() } } + /// + /// Opens an existing file for reading. + /// + /// The file to be opened for reading. + /// A read-only on the specified path. + protected virtual Stream GetFileStream(string path) => +#if WINDOWS_UWP + StorageFile.GetFileFromPathAsync(path).GetResults().OpenReadAsync().GetResults().AsStream(); +#else + File.OpenRead(path); +#endif + /// /// Pushes a file to device /// @@ -461,7 +486,7 @@ protected virtual string SyncPackageToDevice(string localFilePath, Action /// The absolute path to file on local host. /// An optional parameter which, when specified, returns progress notifications. + [EditorBrowsable(EditorBrowsableState.Never)] protected readonly struct SyncProgress(string localFilePath, Action progress) : IProgress { /// diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs index 40d72da0..288a0046 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs @@ -3,14 +3,15 @@ // using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Processes the output of the printenv command, which dumps all environment variables of an Android device. /// - public sealed partial class EnvironmentVariablesReceiver : MultiLineReceiver + public sealed partial class EnvironmentVariablesReceiver : ShellOutputReceiver { /// /// The path to the printenv command. @@ -22,6 +23,11 @@ public sealed partial class EnvironmentVariablesReceiver : MultiLineReceiver /// private const string EnvPattern = @"^([^=\s]+)\s*=\s*(.*)$"; + /// + /// The cached by . + /// + private Regex? regex = null; + /// /// Initializes a new instance of the class. /// @@ -32,34 +38,33 @@ public EnvironmentVariablesReceiver() { } /// public Dictionary EnvironmentVariables { get; } = []; - /// - /// Processes the new lines. - /// - /// The lines. - protected override void ProcessNewLines(IEnumerable lines) + /// + [MemberNotNull(nameof(regex))] + public override bool AddOutput(string line) { - Regex regex = EnvRegex(); - foreach (string line in lines) + regex ??= EnvRegex(); + if (string.IsNullOrEmpty(line) || line.StartsWith('#')) { - if (string.IsNullOrEmpty(line) || line.StartsWith('#')) - { - continue; - } + return true; + } - Match m = regex.Match(line); - if (m.Success) - { - string label = m.Groups[1].Value.Trim(); - string value = m.Groups[2].Value.Trim(); + Match m = regex.Match(line); + if (m.Success) + { + string label = m.Groups[1].Value.Trim(); + string value = m.Groups[2].Value.Trim(); - if (label.Length > 0) - { - EnvironmentVariables[label] = value; - } + if (label.Length > 0) + { + EnvironmentVariables[label] = value; } } + return true; } + /// + protected override void Done() => regex = null; + #if NET7_0_OR_GREATER [GeneratedRegex(EnvPattern)] private static partial Regex EnvRegex(); diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs index a7caf189..8117f9fe 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs @@ -3,14 +3,15 @@ // using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Parses the output of the getprop command, which lists all properties of an Android device. /// - public sealed partial class GetPropReceiver : MultiLineReceiver + public sealed partial class GetPropReceiver : ShellOutputReceiver { /// /// The path to the getprop executable to run on the device. @@ -22,6 +23,11 @@ public sealed partial class GetPropReceiver : MultiLineReceiver /// private const string GetPropPattern = "^\\[([^]]+)\\]\\:\\s*\\[(.*)\\]$"; + /// + /// The cached by . + /// + private Regex? regex = null; + /// /// Initializes a new instance of the class. /// @@ -32,38 +38,38 @@ public GetPropReceiver() { } /// public Dictionary Properties { get; } = []; - /// - /// Processes the new lines. - /// - /// The lines to process. - protected override void ProcessNewLines(IEnumerable lines) + /// + [MemberNotNull(nameof(regex))] + public override bool AddOutput(string line) { - Regex regex = GetPropRegex(); + regex ??= GetPropRegex(); // We receive an array of lines. We're expecting // to have the build info in the first line, and the build // date in the 2nd line. There seems to be an empty line // after all that. - foreach (string line in lines) + if (string.IsNullOrEmpty(line) || line.StartsWith('#') || line.StartsWith('$')) { - if (string.IsNullOrEmpty(line) || line.StartsWith('#') || line.StartsWith('$')) - { - continue; - } - Match m = regex.Match(line); - if (m.Success) - { - string label = m.Groups[1].Value.Trim(); - string value = m.Groups[2].Value.Trim(); + return true; + } + + Match m = regex.Match(line); + if (m.Success) + { + string label = m.Groups[1].Value.Trim(); + string value = m.Groups[2].Value.Trim(); - if (label.Length > 0) - { - Properties[label] = value; - } + if (label.Length > 0) + { + Properties[label] = value; } } + return true; } + /// + protected override void Done() => regex = null; + #if NET7_0_OR_GREATER [GeneratedRegex(GetPropPattern)] private static partial Regex GetPropRegex(); diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs index f99485af..9f1f8c6e 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Processes command line output of a adb shell command. /// - public class InfoOutputReceiver : MultiLineReceiver + public class InfoOutputReceiver : ShellOutputReceiver { /// /// Initializes a new instance of the class. @@ -56,28 +56,23 @@ public InfoOutputReceiver() { } /// Function parsing one string and returning the property value if possible. public void AddPropertyParser(string property, Func parser) => PropertyParsers[property] = parser; - /// - /// Processes the new lines, and sets version information if the line represents package information data. - /// - /// The lines to process. - protected override void ProcessNewLines(IEnumerable lines) + /// + public override bool AddOutput(string line) { - foreach (string line in lines) + if (line == null) { - if (line == null) - { - continue; - } + return true; + } - foreach (KeyValuePair> parser in PropertyParsers) + foreach (KeyValuePair> parser in PropertyParsers) + { + object? propertyValue = parser.Value(line); + if (propertyValue != null) { - object? propertyValue = parser.Value(line); - if (propertyValue != null) - { - Properties[parser.Key] = propertyValue; - } + Properties[parser.Key] = propertyValue; } } + return true; } } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs index 29e91c98..909bf6ef 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs @@ -2,15 +2,14 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // -using System.Collections.Generic; using System.Text.RegularExpressions; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Processes output of the pm install command. /// - public partial class InstallOutputReceiver : MultiLineReceiver + public partial class InstallOutputReceiver : ShellOutputReceiver { /// /// The error message that indicates an unknown error occurred. @@ -65,67 +64,64 @@ public InstallOutputReceiver() { } /// public bool Success { get; private set; } - /// - /// Processes the new lines. - /// - /// The lines. - protected override void ProcessNewLines(IEnumerable lines) + /// + public override bool AddOutput(string line) { - Regex successRegex = SuccessRegex(); - Regex failureRegex = FailureRegex(); - Regex errorRegex = ErrorRegex(); - - foreach (string line in lines) + if (line.Length > 0) { - if (line.Length > 0) + if (line.StartsWith(SuccessOutput)) { - if (line.StartsWith(SuccessOutput)) - { - Match m = successRegex.Match(line); - SuccessMessage = string.Empty; + Regex successRegex = SuccessRegex(); + Match m = successRegex.Match(line); + SuccessMessage = string.Empty; - ErrorMessage = null; + ErrorMessage = null; - if (m.Success) - { - string msg = m.Groups[1].Value; - SuccessMessage = msg ?? string.Empty; - } - - Success = true; - } - else if (line.StartsWith(FailureOutput)) + if (m.Success) { - Match m = failureRegex.Match(line); - ErrorMessage = UnknownError; + string msg = m.Groups[1].Value; + SuccessMessage = msg ?? string.Empty; + } - SuccessMessage = null; + Success = true; + return false; + } + else if (line.StartsWith(FailureOutput)) + { + Regex failureRegex = FailureRegex(); + Match m = failureRegex.Match(line); + ErrorMessage = UnknownError; - if (m.Success) - { - string msg = m.Groups[1].Value; - ErrorMessage = StringExtensions.IsNullOrWhiteSpace(msg) ? UnknownError : msg; - } + SuccessMessage = null; - Success = false; - } - else + if (m.Success) { - Match m = errorRegex.Match(line); - ErrorMessage = UnknownError; + string msg = m.Groups[1].Value; + ErrorMessage = StringExtensions.IsNullOrWhiteSpace(msg) ? UnknownError : msg; + } - SuccessMessage = null; + Success = false; + return false; + } + else + { + Regex errorRegex = ErrorRegex(); + Match m = errorRegex.Match(line); + ErrorMessage = UnknownError; - if (m.Success) - { - string msg = m.Groups[1].Value; - ErrorMessage = StringExtensions.IsNullOrWhiteSpace(msg) ? UnknownError : msg; - } + SuccessMessage = null; - Success = false; + if (m.Success) + { + string msg = m.Groups[1].Value; + ErrorMessage = StringExtensions.IsNullOrWhiteSpace(msg) ? UnknownError : msg; } + + Success = false; + return false; } } + return true; } #if NET7_0_OR_GREATER diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/NamespaceDoc.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/NamespaceDoc.cs index 8c2de411..c29a9c31 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/NamespaceDoc.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/NamespaceDoc.cs @@ -5,13 +5,13 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// - /// The classes in this namespace provide receivers for . + /// The classes in this namespace provide receivers for . /// /// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. [CompilerGenerated] [EditorBrowsable(EditorBrowsableState.Never)] - internal class NamespaceDoc : Receivers.NamespaceDoc { } + internal class NamespaceDoc : AdvancedSharpAdbClient.Receivers.NamespaceDoc { } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs index 9ea82c3b..0a4308d0 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Parses the output of the various pm commands. @@ -22,10 +22,7 @@ public class PackageManagerReceiver(PackageManager packageManager) : MultiLineRe /// public PackageManager PackageManager { get; } = packageManager; - /// - /// Processes the new lines. - /// - /// The lines. + /// protected override void ProcessNewLines(IEnumerable lines) { PackageManager.Packages.Clear(); diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs index eb459f7f..9b2a700c 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs @@ -4,12 +4,12 @@ using System.Collections.Generic; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Parses the output of a cat /proc/[pid]/stat command. /// - public class ProcessOutputReceiver : MultiLineReceiver + public class ProcessOutputReceiver : ShellOutputReceiver { /// /// Initializes a new instance of the class. @@ -22,25 +22,23 @@ public ProcessOutputReceiver() { } public List Processes { get; } = []; /// - protected override void ProcessNewLines(IEnumerable lines) + public override bool AddOutput(string line) { - foreach (string line in lines) + // Process has already died (e.g. the cat process itself) + if (line.Contains("No such file or directory")) { - // Process has already died (e.g. the cat process itself) - if (line.Contains("No such file or directory")) - { - continue; - } + return false; + } - try - { - Processes.Add(new AndroidProcess(line, cmdLinePrefix: true)); - } - catch - { - // Swallow - } + try + { + Processes.Add(new AndroidProcess(line, cmdLinePrefix: true)); + } + catch + { + // Swallow } + return true; } } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs index c9d4d37c..91481939 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs @@ -5,7 +5,7 @@ using System; using System.Text.RegularExpressions; -namespace AdvancedSharpAdbClient.Receivers.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Receivers { /// /// Processes command line output of the dumpsys package command. diff --git a/AdvancedSharpAdbClient/DeviceMonitor.Async.cs b/AdvancedSharpAdbClient/DeviceMonitor.Async.cs index 1f6f6b2a..23edc8e7 100644 --- a/AdvancedSharpAdbClient/DeviceMonitor.Async.cs +++ b/AdvancedSharpAdbClient/DeviceMonitor.Async.cs @@ -45,7 +45,7 @@ public virtual async Task StartAsync(CancellationToken cancellationToken = defau } /// - /// Stops the monitoring + /// Asynchronously stops the monitoring /// protected virtual async Task DisposeAsyncCore() { @@ -102,7 +102,7 @@ public async Task DisposeAsync() #endif /// - /// Monitors the devices. This connects to the Debug Bridge + /// Asynchronously monitors the devices. This connects to the Debug Bridge /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. diff --git a/AdvancedSharpAdbClient/Exceptions/JavaException.cs b/AdvancedSharpAdbClient/Exceptions/JavaException.cs index 2da3e4d1..83d63312 100644 --- a/AdvancedSharpAdbClient/Exceptions/JavaException.cs +++ b/AdvancedSharpAdbClient/Exceptions/JavaException.cs @@ -85,7 +85,6 @@ public JavaException(SerializationInfo serializationInfo, StreamingContext conte /// The equivalent . public static JavaException Parse(string line) => Parse(line.Split(separator, StringSplitOptions.RemoveEmptyEntries)); - /// /// Creates a from it representation. /// diff --git a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs index be2808eb..d6220671 100644 --- a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs +++ b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs @@ -4,9 +4,6 @@ // using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Net; using System.Threading; @@ -15,7 +12,7 @@ namespace AdvancedSharpAdbClient public static partial class AdbClientExtensions { /// - /// Asks the ADB server to forward local connections from + /// Asynchronously asks the ADB server to forward local connections from /// to the address on the . /// /// An instance of a class that implements the interface. @@ -32,7 +29,7 @@ public static Task CreateForwardAsync(this IAdbClient client, DeviceData de client.CreateForwardAsync(device, local.ToString(), remote.ToString(), allowRebind, cancellationToken); /// - /// Creates a port forwarding between a local and a remote port. + /// Asynchronously creates a port forwarding between a local and a remote port. /// /// An instance of a class that implements the interface. /// The device to which to forward the connections. @@ -47,7 +44,7 @@ public static Task CreateForwardAsync(this IAdbClient client, DeviceData de client.CreateForwardAsync(device, $"tcp:{localPort}", $"tcp:{remotePort}", true, cancellationToken); /// - /// Forwards a remote Unix socket to a local TCP socket. + /// Asynchronously forwards a remote Unix socket to a local TCP socket. /// /// An instance of a class that implements the interface. /// The device to which to forward the connections. @@ -63,7 +60,7 @@ public static Task CreateForwardAsync(this IAdbClient client, DeviceData de client.CreateForwardAsync(device, $"tcp:{localPort}", $"local:{remoteSocket}", true, cancellationToken); /// - /// Asks the ADB server to reverse forward local connections from + /// Asynchronously asks the ADB server to reverse forward local connections from /// to the address on the . /// /// An instance of a class that implements the interface. @@ -80,7 +77,7 @@ public static Task CreateReverseForwardAsync(this IAdbClient client, Device client.CreateReverseForwardAsync(device, remote.ToString(), local.ToString(), allowRebind, cancellationToken); /// - /// Remove a reverse port forwarding between a remote and a local port. + /// Asynchronously remove a reverse port forwarding between a remote and a local port. /// /// An instance of a class that implements the interface. /// The device on which to remove the reverse port forwarding @@ -91,7 +88,7 @@ public static Task RemoveReverseForwardAsync(this IAdbClient client, DeviceData client.RemoveReverseForwardAsync(device, remote.ToString(), cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// An instance of a class that implements the interface. /// The target of command, such as shell, remount, dev, tcp, local, @@ -104,7 +101,7 @@ public static Task ExecuteServerCommandAsync(this IAdbClient client, string targ client.ExecuteServerCommandAsync(target, command, receiver, AdbClient.Encoding, cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// An instance of a class that implements the interface. /// The target of command, such as shell, remount, dev, tcp, local, @@ -117,7 +114,7 @@ public static Task ExecuteServerCommandAsync(this IAdbClient client, string targ client.ExecuteServerCommandAsync(target, command, socket, AdbClient.Encoding, cancellationToken); /// - /// Executes a command on the device. + /// Asynchronously executes a command on the device. /// /// An instance of a class that implements the interface. /// The command to execute. @@ -128,7 +125,7 @@ public static Task ExecuteRemoteCommandAsync(this IAdbClient client, string comm client.ExecuteRemoteCommandAsync(command, device, AdbClient.Encoding, cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// An instance of a class that implements the interface. /// The target of command, such as shell, remount, dev, tcp, local, @@ -140,7 +137,7 @@ public static Task ExecuteServerCommandAsync(this IAdbClient client, string targ client.ExecuteServerCommandAsync(target, command, AdbClient.Encoding, cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// An instance of a class that implements the interface. /// The target of command, such as shell, remount, dev, tcp, local, @@ -154,7 +151,7 @@ public static Task ExecuteServerCommandAsync(this IAdbClient client, string targ client.ExecuteServerCommandAsync(target, command, socket, receiver, AdbClient.Encoding, cancellationToken); /// - /// Executes a command on the device. + /// Asynchronously executes a command on the device. /// /// An instance of a class that implements the interface. /// The command to execute. @@ -177,7 +174,7 @@ public static Task RunLogServiceAsync(this IAdbClient client, DeviceData device, client.RunLogServiceAsync(device, messageSink, default, logNames); /// - /// Reboots the specified adb socket address. + /// Asynchronously reboots the specified adb socket address. /// /// An instance of a class that implements the interface. /// The device. @@ -186,7 +183,7 @@ public static Task RunLogServiceAsync(this IAdbClient client, DeviceData device, public static Task RebootAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) => client.RebootAsync(string.Empty, device, cancellationToken); /// - /// Pair with a device for secure TCP/IP communication. + /// Asynchronously pair with a device for secure TCP/IP communication. /// /// An instance of a class that implements the interface. /// The IP address of the remote device. @@ -199,7 +196,7 @@ public static Task PairAsync(this IAdbClient client, IPAddress address, : client.PairAsync(new IPEndPoint(address, AdbClient.DefaultPort), code, cancellationToken); /// - /// Pair with a device for secure TCP/IP communication. + /// Asynchronously pair with a device for secure TCP/IP communication. /// /// An instance of a class that implements the interface. /// The DNS endpoint at which the adb server on the device is running. @@ -212,7 +209,7 @@ public static Task PairAsync(this IAdbClient client, IPEndPoint endpoint : client.PairAsync(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port), code, cancellationToken); /// - /// Pair with a device for secure TCP/IP communication. + /// Asynchronously pair with a device for secure TCP/IP communication. /// /// An instance of a class that implements the interface. /// The host address of the remote device. @@ -223,7 +220,7 @@ public static Task PairAsync(this IAdbClient client, string host, string client.PairAsync(Extensions.CreateDnsEndPoint(host, AdbClient.DefaultPort), code, cancellationToken); /// - /// Pair with a device for secure TCP/IP communication. + /// Asynchronously pair with a device for secure TCP/IP communication. /// /// An instance of a class that implements the interface. /// The host address of the remote device. @@ -235,7 +232,7 @@ public static Task PairAsync(this IAdbClient client, string host, int po client.PairAsync(Extensions.CreateDnsEndPoint(host, port), code, cancellationToken); /// - /// Connect to a device via TCP/IP. + /// Asynchronously connect to a device via TCP/IP. /// /// An instance of a class that implements the interface. /// The IP address of the remote device. @@ -247,7 +244,7 @@ public static Task ConnectAsync(this IAdbClient client, IPAddress addres : client.ConnectAsync(new IPEndPoint(address, AdbClient.DefaultPort), cancellationToken); /// - /// Connect to a device via TCP/IP. + /// Asynchronously connect to a device via TCP/IP. /// /// An instance of a class that implements the interface. /// The IP endpoint at which the adb server on the device is running. @@ -259,7 +256,7 @@ public static Task ConnectAsync(this IAdbClient client, IPEndPoint endpo : client.ConnectAsync(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port), cancellationToken); /// - /// Connect to a device via TCP/IP. + /// Asynchronously connect to a device via TCP/IP. /// /// An instance of a class that implements the interface. /// The host address of the remote device. @@ -270,41 +267,7 @@ public static Task ConnectAsync(this IAdbClient client, string host, int client.ConnectAsync(Extensions.CreateDnsEndPoint(host, port), cancellationToken); /// - /// Asynchronously installs an Android application on an device. - /// - /// An instance of a class that implements the interface. - /// The device on which to install the application. - /// A which represents the application to install. - /// The arguments to pass to adb install. - /// A which represents the asynchronous operation. - public static Task InstallAsync(this IAdbClient client, DeviceData device, Stream apk, params string[] arguments) => client.InstallAsync(device, apk, default, arguments); - - /// - /// Asynchronously push multiple APKs to the device and install them. - /// - /// An instance of a class that implements the interface. - /// The device on which to install the application. - /// s which represents the split APKs to install. - /// The package name of the base APK to install. - /// The arguments to pass to adb install-create. - /// A which represents the asynchronous operation. - public static Task InstallMultipleAsync(this IAdbClient client, DeviceData device, IEnumerable splitAPKs, string packageName, params string[] arguments) => - client.InstallMultipleAsync(device, splitAPKs, packageName, default, arguments); - - /// - /// Asynchronously push multiple APKs to the device and install them. - /// - /// An instance of a class that implements the interface. - /// The device on which to install the application. - /// A which represents the base APK to install. - /// s which represents the split APKs to install. - /// The arguments to pass to adb install-create. - /// A which represents the asynchronous operation. - public static Task InstallMultipleAsync(this IAdbClient client, DeviceData device, Stream baseAPK, IEnumerable splitAPKs, params string[] arguments) => - client.InstallMultipleAsync(device, baseAPK, splitAPKs, default, arguments); - - /// - /// Like "install", but starts an install session. + /// Like "install", but starts an install session synchronously. /// /// An instance of a class that implements the interface. /// The device on which to install the application. @@ -315,7 +278,7 @@ public static Task InstallCreateAsync(this IAdbClient client, DeviceData client.InstallCreateAsync(device, packageName, default, arguments); /// - /// Uninstalls an Android application on an device. + /// Asynchronously uninstalls an Android application on an device. /// /// An instance of a class that implements the interface. /// The device on which to install the application. @@ -324,38 +287,6 @@ public static Task InstallCreateAsync(this IAdbClient client, DeviceData /// A which represents the asynchronous operation. public static Task UninstallAsync(this IAdbClient client, DeviceData device, string packageName, params string[] arguments) => client.UninstallAsync(device, packageName, default, arguments); - - /// - /// Clear the input text. The input should be in focus. Use if the element isn't focused. - /// - /// An instance of a class that implements the interface. - /// The device on which to clear the input text. - /// The length of text to clear. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - public static async Task ClearInputAsync(this IAdbClient client, DeviceData device, int charCount, CancellationToken cancellationToken = default) - { - await client.SendKeyEventAsync(device, "KEYCODE_MOVE_END", cancellationToken).ConfigureAwait(false); - await client.SendKeyEventAsync(device, StringExtensions.Join(" ", Enumerable.Repeat("KEYCODE_DEL", charCount)), cancellationToken).ConfigureAwait(false); - } - - /// - /// Click BACK button. - /// - /// An instance of a class that implements the interface. - /// The device on which to click BACK button. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - public static Task ClickBackButtonAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) => client.SendKeyEventAsync(device, "KEYCODE_BACK", cancellationToken); - - /// - /// Click HOME button. - /// - /// An instance of a class that implements the interface. - /// The device on which to click HOME button. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - public static Task ClickHomeButtonAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) => client.SendKeyEventAsync(device, "KEYCODE_HOME", cancellationToken); } } #endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs index 7cb1158b..20f79380 100644 --- a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs @@ -3,7 +3,6 @@ // using System; -using System.Linq; using System.Net; namespace AdvancedSharpAdbClient @@ -225,31 +224,5 @@ public static string Connect(this IAdbClient client, IPEndPoint endpoint) => /// The results from adb. public static string Connect(this IAdbClient client, string host, int port = AdbClient.DefaultPort) => client.Connect(Extensions.CreateDnsEndPoint(host, port)); - - /// - /// Clear the input text. The input should be in focus. Use if the element isn't focused. - /// - /// An instance of a class that implements the interface. - /// The device on which to clear the input text. - /// The length of text to clear. - public static void ClearInput(this IAdbClient client, DeviceData device, int charCount) - { - client.SendKeyEvent(device, "KEYCODE_MOVE_END"); - client.SendKeyEvent(device, StringExtensions.Join(" ", Enumerable.Repeat("KEYCODE_DEL", charCount))); - } - - /// - /// Click BACK button. - /// - /// An instance of a class that implements the interface. - /// The device on which to click BACK button. - public static void ClickBackButton(this IAdbClient client, DeviceData device) => client.SendKeyEvent(device, "KEYCODE_BACK"); - - /// - /// Click HOME button. - /// - /// An instance of a class that implements the interface. - /// The device on which to click HOME button. - public static void ClickHomeButton(this IAdbClient client, DeviceData device) => client.SendKeyEvent(device, "KEYCODE_HOME"); } } diff --git a/AdvancedSharpAdbClient/Extensions/Extensions.cs b/AdvancedSharpAdbClient/Extensions/Extensions.cs index 487f5ac0..d6dd2604 100644 --- a/AdvancedSharpAdbClient/Extensions/Extensions.cs +++ b/AdvancedSharpAdbClient/Extensions/Extensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Net; using System.Net.Sockets; @@ -20,6 +21,7 @@ namespace AdvancedSharpAdbClient /// /// Extension methods for the namespace. /// + [EditorBrowsable(EditorBrowsableState.Never)] internal static class Extensions { public static char[] NewLineSeparator { get; } = ['\r', '\n']; @@ -139,6 +141,20 @@ public static YieldAwaitable Yield() => #endif .Yield(); + /// + /// Creates a System.Threading.Tasks.Task`1 that's completed successfully with the specified result. + /// + /// The type of the result returned by the task. + /// The result to store into the completed task. + /// The successfully completed task. + public static Task FromResult(TResult result) => +#if NETFRAMEWORK && !NET45_OR_GREATER + TaskEx +#else + Task +#endif + .FromResult(result); + #if !NET7_0_OR_GREATER /// /// Reads a line of characters asynchronously and returns the data as a string. diff --git a/AdvancedSharpAdbClient/Extensions/Factories.cs b/AdvancedSharpAdbClient/Extensions/Factories.cs index 538bea74..e1f38a59 100644 --- a/AdvancedSharpAdbClient/Extensions/Factories.cs +++ b/AdvancedSharpAdbClient/Extensions/Factories.cs @@ -16,11 +16,6 @@ public static class Factories { static Factories() => Reset(); - /// - /// Determines whether the specified file exists. - /// - public static Func CheckFileExists { get; set; } - /// /// Gets or sets a delegate which creates a new instance of the class. /// @@ -51,14 +46,12 @@ public static class Factories /// Resets all factories to their default values. /// [MemberNotNull( - nameof(CheckFileExists), nameof(AdbSocketFactory), nameof(AdbClientFactory), nameof(AdbCommandLineClientFactory), nameof(SyncServiceFactory))] public static void Reset() { - CheckFileExists = File.Exists; AdbSocketFactory = endPoint => new AdbSocket(endPoint); AdbClientFactory = endPoint => new AdbClient(endPoint, AdbSocketFactory); AdbCommandLineClientFactory = path => new AdbCommandLineClient(path); diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs index 6d3859fb..e8016534 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs @@ -7,40 +7,37 @@ using System.Collections.Generic; using System.IO; using System.Net; -using System.Runtime.CompilerServices; using System.Text; using System.Threading; -using System.Xml; namespace AdvancedSharpAdbClient { public partial interface IAdbClient { /// - /// Ask the ADB server for its internal version number. + /// Asynchronously ask the ADB server for its internal version number. /// /// A which can be used to cancel the asynchronous operation. /// A which return the ADB version number. Task GetAdbVersionAsync(CancellationToken cancellationToken); /// - /// Ask the ADB server to quit immediately. This is used when the - /// ADB client detects that an obsolete server is running after an - /// upgrade. + /// Asynchronously ask the ADB server to quit immediately. This is used when the + /// ADB client detects that an obsolete server is running after an upgrade. /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. Task KillAdbAsync(CancellationToken cancellationToken); /// - /// Gets the devices that are available for communication. + /// Asynchronously gets the devices that are available for communication. /// /// A which can be used to cancel the asynchronous operation. /// A which return the list of devices that are connected. Task> GetDevicesAsync(CancellationToken cancellationToken); /// - /// Asks the ADB server to forward local connections from + /// Asynchronously asks the ADB server to forward local connections from /// to the address on the . /// /// The device on which to forward the connections. @@ -78,7 +75,7 @@ public partial interface IAdbClient Task CreateForwardAsync(DeviceData device, string local, string remote, bool allowRebind, CancellationToken cancellationToken); /// - /// Asks the ADB server to reverse forward local connections from + /// Asynchronously asks the ADB server to reverse forward local connections from /// to the address on the . /// /// The device on which to reverse forward the connections. @@ -116,7 +113,7 @@ public partial interface IAdbClient Task CreateReverseForwardAsync(DeviceData device, string remote, string local, bool allowRebind, CancellationToken cancellationToken); /// - /// Remove a reverse port forwarding between a remote and a local port. + /// Asynchronously remove a reverse port forwarding between a remote and a local port. /// /// The device on which to remove the reverse port forwarding /// Specification of the remote that was forwarded @@ -125,7 +122,7 @@ public partial interface IAdbClient Task RemoveReverseForwardAsync(DeviceData device, string remote, CancellationToken cancellationToken); /// - /// Removes all reverse forwards for a given device. + /// Asynchronously removes all reverse forwards for a given device. /// /// The device on which to remove all reverse port forwarding /// A which can be used to cancel the asynchronous operation. @@ -133,7 +130,7 @@ public partial interface IAdbClient Task RemoveAllReverseForwardsAsync(DeviceData device, CancellationToken cancellationToken); /// - /// Remove a port forwarding between a local and a remote port. + /// Asynchronously remove a port forwarding between a local and a remote port. /// /// The device on which to remove the port forwarding. /// Specification of the local port that was forwarded. @@ -142,7 +139,7 @@ public partial interface IAdbClient Task RemoveForwardAsync(DeviceData device, int localPort, CancellationToken cancellationToken); /// - /// Removes all forwards for a given device. + /// Asynchronously removes all forwards for a given device. /// /// The device on which to remove the port forwarding. /// A which can be used to cancel the asynchronous operation. @@ -150,7 +147,7 @@ public partial interface IAdbClient Task RemoveAllForwardsAsync(DeviceData device, CancellationToken cancellationToken); /// - /// List all existing forward connections from this server. + /// Asynchronously list all existing forward connections from this server. /// /// The device for which to list the existing forward connections. /// A which can be used to cancel the asynchronous operation. @@ -166,7 +163,7 @@ public partial interface IAdbClient Task> ListReverseForwardAsync(DeviceData device, CancellationToken cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// The target of command, such as shell, remount, dev, tcp, local, /// localreserved, localabstract, jdwp, track-jdwp, sync, reverse and so on. @@ -177,7 +174,7 @@ public partial interface IAdbClient Task ExecuteServerCommandAsync(string target, string command, Encoding encoding, CancellationToken cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// The target of command, such as shell, remount, dev, tcp, local, /// localreserved, localabstract, jdwp, track-jdwp, sync, reverse and so on. @@ -189,7 +186,7 @@ public partial interface IAdbClient Task ExecuteServerCommandAsync(string target, string command, IAdbSocket socket, Encoding encoding, CancellationToken cancellationToken); /// - /// Executes a command on the device. + /// Asynchronously executes a command on the device. /// /// The command to execute. /// The device on which to run the command. @@ -199,7 +196,7 @@ public partial interface IAdbClient Task ExecuteRemoteCommandAsync(string command, DeviceData device, Encoding encoding, CancellationToken cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// The target of command, such as shell, remount, dev, tcp, local, /// localreserved, localabstract, jdwp, track-jdwp, sync, reverse and so on. @@ -211,7 +208,7 @@ public partial interface IAdbClient Task ExecuteServerCommandAsync(string target, string command, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken); /// - /// Executes a command on the adb server. + /// Asynchronously executes a command on the adb server. /// /// The target of command, such as shell, remount, dev, tcp, local, /// localreserved, localabstract, jdwp, track-jdwp, sync, reverse and so on. @@ -224,7 +221,7 @@ public partial interface IAdbClient Task ExecuteServerCommandAsync(string target, string command, IAdbSocket socket, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken); /// - /// Executes a command on the device. + /// Asynchronously executes a command on the device. /// /// The command to execute. /// The device on which to run the command. @@ -235,7 +232,7 @@ public partial interface IAdbClient Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellOutputReceiver receiver, Encoding encoding, CancellationToken cancellationToken); /// - /// Gets the frame buffer from the specified end point. + /// Asynchronously gets the frame buffer from the specified end point. /// /// The device for which to get the framebuffer. /// A that can be used to cancel the asynchronous task. @@ -255,7 +252,7 @@ public partial interface IAdbClient Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames); /// - /// Reboots the specified device in to the specified mode. + /// Asynchronously reboots the specified device in to the specified mode. /// /// The mode into which to reboot the device. /// The device to reboot. @@ -264,7 +261,7 @@ public partial interface IAdbClient Task RebootAsync(string into, DeviceData device, CancellationToken cancellationToken); /// - /// Pair with a device for secure TCP/IP communication + /// Asynchronously pair with a device for secure TCP/IP communication /// /// The DNS endpoint at which the adb server on the device is running. /// The pairing code. @@ -273,7 +270,7 @@ public partial interface IAdbClient Task PairAsync(DnsEndPoint endpoint, string code, CancellationToken cancellationToken); /// - /// Connect to a device via TCP/IP. + /// Asynchronously connect to a device via TCP/IP. /// /// The DNS endpoint at which the adb server on the device is running. /// A which can be used to cancel the asynchronous operation. @@ -281,7 +278,7 @@ public partial interface IAdbClient Task ConnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken); /// - /// Disconnects a remote device from this local ADB server. + /// Asynchronously disconnects a remote device from this local ADB server. /// /// The endpoint of the remote device to disconnect. /// A which can be used to cancel the asynchronous operation. @@ -289,7 +286,7 @@ public partial interface IAdbClient Task DisconnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken); /// - /// Restarts the ADB daemon running on the device with root privileges. + /// Asynchronously restarts the ADB daemon running on the device with root privileges. /// /// The device on which to restart ADB with root privileges. /// A which can be used to cancel the asynchronous operation. @@ -297,7 +294,7 @@ public partial interface IAdbClient Task RootAsync(DeviceData device, CancellationToken cancellationToken); /// - /// Restarts the ADB daemon running on the device without root privileges. + /// Asynchronously restarts the ADB daemon running on the device without root privileges. /// /// The device on which to restart ADB without root privileges. /// A which can be used to cancel the asynchronous operation. @@ -309,35 +306,41 @@ public partial interface IAdbClient /// /// The device on which to install the application. /// A which represents the application to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install. /// A which represents the asynchronous operation. - Task InstallAsync(DeviceData device, Stream apk, CancellationToken cancellationToken, params string[] arguments); + Task InstallAsync(DeviceData device, Stream apk, IProgress? progress, CancellationToken cancellationToken, params string[] arguments); /// /// Asynchronously push multiple APKs to the device and install them. /// /// The device on which to install the application. + /// A which represents the base APK to install. /// s which represents the split APKs to install. - /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install-create. /// A which represents the asynchronous operation. - Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, CancellationToken cancellationToken, params string[] arguments); + Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress, CancellationToken cancellationToken, params string[] arguments); /// /// Asynchronously push multiple APKs to the device and install them. /// /// The device on which to install the application. - /// A which represents the base APK to install. /// s which represents the split APKs to install. + /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install-create. /// A which represents the asynchronous operation. - Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, CancellationToken cancellationToken, params string[] arguments); + Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress, CancellationToken cancellationToken, params string[] arguments); /// - /// Like "install", but starts an install session. + /// Like "install", but starts an install session asynchronously. /// /// The device on which to install the application. /// The package name of the baseAPK to install. @@ -347,18 +350,20 @@ public partial interface IAdbClient Task InstallCreateAsync(DeviceData device, string? packageName, CancellationToken cancellationToken, params string[] arguments); /// - /// Write an apk into the given install session. + /// Asynchronously write an apk into the given install session. /// /// The device on which to install the application. /// A which represents the application to install. /// The name of the application. /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. - Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, CancellationToken cancellationToken); + Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, IProgress? progress, CancellationToken cancellationToken); /// - /// Commit the given active install session, installing the app. + /// Asynchronously commit the given active install session, installing the app. /// /// The device on which to install the application. /// The session ID of the install session. @@ -367,7 +372,7 @@ public partial interface IAdbClient Task InstallCommitAsync(DeviceData device, string session, CancellationToken cancellationToken); /// - /// Uninstalls an Android application on an device. + /// Asynchronously uninstalls an Android application on an device. /// /// The device on which to install the application. /// The name of the package to uninstall. @@ -377,192 +382,13 @@ public partial interface IAdbClient Task UninstallAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments); /// - /// Lists all features supported by the current device. + /// Asynchronously lists all features supported by the current device. /// /// The device for which to get the list of features supported. /// A which can be used to cancel the asynchronous operation. /// A which return the list of all features supported by the current device. Task> GetFeatureSetAsync(DeviceData device, CancellationToken cancellationToken); - /// - /// Gets the current device screen snapshot asynchronously. - /// - /// The device for which to get the screen snapshot. - /// A which can be used to cancel the asynchronous operation. - /// A which return a containing current hierarchy. - /// Failed if start with ERROR or java.lang.Exception. - Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken); - - /// - /// Gets the current device screen snapshot asynchronously. - /// - /// The device for which to get the screen snapshot. - /// A which can be used to cancel the asynchronous operation. - /// A which return a containing current hierarchy. - Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken); - -#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER - /// - /// Gets the current device screen snapshot asynchronously. - /// - /// The device for which to get the screen snapshot. - /// A which can be used to cancel the asynchronous operation. - /// A which return a containing current hierarchy. - Task DumpScreenWinRTAsync(DeviceData device, CancellationToken cancellationToken) -#if WINDOWS10_0_17763_0_OR_GREATER - => DumpScreenAsync(device, cancellationToken).ContinueWith(x => - { - Windows.Data.Xml.Dom.XmlDocument doc = new(); - doc.LoadXml(x.Result?.OuterXml); - return doc; - })! -#endif - ; -#endif - - /// - /// Clicks on the specified coordinates. - /// - /// The device on which to click. - /// The to click. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task ClickAsync(DeviceData device, Point cords, CancellationToken cancellationToken); - - /// - /// Clicks on the specified coordinates. - /// - /// The device on which to click. - /// The X co-ordinate to click. - /// The Y co-ordinate to click. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task ClickAsync(DeviceData device, int x, int y, CancellationToken cancellationToken); - - /// - /// Generates a swipe gesture from first element to second element Specify the speed in ms. - /// - /// The device on which to swipe. - /// The start element. - /// The end element. - /// The time spent in swiping. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task SwipeAsync(DeviceData device, Element first, Element second, long speed, CancellationToken cancellationToken); - - /// - /// Generates a swipe gesture from co-ordinates x1,y1 to x2,y2 with speed Specify the speed in ms. - /// - /// The device on which to swipe. - /// The start X co-ordinate. - /// The start Y co-ordinate. - /// The end X co-ordinate. - /// The end Y co-ordinate. - /// The time spent in swiping. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task SwipeAsync(DeviceData device, int x1, int y1, int x2, int y2, long speed, CancellationToken cancellationToken); - - /// - /// Check if the app is running in foreground. - /// - /// The device on which to check. - /// The package name of the app to check. - /// A which can be used to cancel the asynchronous operation. - /// A which return the result. if the app is running in foreground; otherwise, . - Task IsAppInForegroundAsync(DeviceData device, string packageName, CancellationToken cancellationToken); - - /// - /// Check if the app is running in background. - /// - /// The device on which to check. - /// The package name of the app to check. - /// A which can be used to cancel the asynchronous operation. - /// A which return the result. if the app is running in background; otherwise, . - Task IsAppRunningAsync(DeviceData device, string packageName, CancellationToken cancellationToken); - - /// - /// Get the of the app. - /// - /// The device on which to get status. - /// The package name of the app to check. - /// A which can be used to cancel the asynchronous operation. - /// A which return the of the app. Foreground, stopped or running in background. - Task GetAppStatusAsync(DeviceData device, string packageName, CancellationToken cancellationToken); - - /// - /// Get element by xpath asynchronously. You can specify the waiting time in timeout. - /// - /// - /// - /// A which can be used to cancel the asynchronous operation. - /// Only check once if . Or it will continue check until is . - /// A which return the of . - Task FindElementAsync(DeviceData device, string xpath, CancellationToken cancellationToken); - - /// - /// Get elements by xpath asynchronously. You can specify the waiting time in timeout. - /// - /// The device on which to get elements. - /// The xpath of the elements. - /// A which can be used to cancel the asynchronous operation. - /// Only check once if . Or it will continue check until is . - /// A which return the of has got. - Task> FindElementsAsync(DeviceData device, string xpath, CancellationToken cancellationToken); - -#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER - /// - /// Get elements by xpath asynchronously. You can specify the waiting time in timeout. - /// - /// The device on which to get elements. - /// The xpath of the elements. - /// A which can be used to cancel the asynchronous operation. - /// Only check once if . Or it will continue check until is . - /// A which return the of has got. - async IAsyncEnumerable FindAsyncElements(DeviceData device, string xpath, [EnumeratorCancellation] CancellationToken cancellationToken) - { - foreach (Element element in await FindElementsAsync(device, xpath, cancellationToken).ConfigureAwait(false)) - { - yield return element; - } - } -#endif - - /// - /// Send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. - /// - /// The device on which to send key event. - /// The key event to send. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task SendKeyEventAsync(DeviceData device, string key, CancellationToken cancellationToken); - - /// - /// Send text to device. Doesn't support Russian. - /// - /// The device on which to send text. - /// The text to send. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task SendTextAsync(DeviceData device, string text, CancellationToken cancellationToken); - - /// - /// Start an Android application on device. - /// - /// The device on which to start an application. - /// The package name of the application to start. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task StartAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken); - - /// - /// Stop an Android application on device. - /// - /// The device on which to stop an application. - /// The package name of the application to stop. - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - Task StopAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken); } } #endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs index 8edbc4c7..1681474c 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs @@ -8,7 +8,6 @@ using System.IO; using System.Net; using System.Text; -using System.Xml; namespace AdvancedSharpAdbClient { @@ -18,6 +17,12 @@ namespace AdvancedSharpAdbClient /// public partial interface IAdbClient { + // remount: not implemented + // dev: not implemented + // track-jdwp: not implemented + // localreserved: not implemented + // localabstract: not implemented + /// /// Gets the at which the Android Debug Bridge server is listening. /// @@ -237,15 +242,6 @@ public partial interface IAdbClient /// The encoding to use when parsing the command output. void ExecuteRemoteCommand(string command, DeviceData device, IShellOutputReceiver receiver, Encoding encoding); - // shell: not implemented - // remount: not implemented - // dev: not implemented - // tcp: not implemented - // tcp:: not implemented - // local: not implemented - // localreserved: not implemented - // localabstract: not implemented - /// /// Gets a which contains the framebuffer data for this device. The framebuffer data can be refreshed, /// giving you high performance access to the device's framebuffer. @@ -270,12 +266,7 @@ public partial interface IAdbClient /// A callback which will receive the event log messages as they are received. /// Optionally, the names of the logs to receive. void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames); - - // jdwp:: not implemented - // track-jdwp: not implemented - // sync: not implemented - // reverse:: not implemented - + /// /// Reboots the specified device in to the specified mode. /// @@ -322,26 +313,32 @@ public partial interface IAdbClient /// /// The device on which to install the application. /// A which represents the application to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install. - void Install(DeviceData device, Stream apk, params string[] arguments); + void Install(DeviceData device, Stream apk, IProgress? progress, params string[] arguments); /// /// Push multiple APKs to the device and install them. /// /// The device on which to install the application. + /// A which represents the base APK to install. /// s which represents the split APKs to install. - /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install-create. - void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, params string[] arguments); + void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress, params string[] arguments); /// /// Push multiple APKs to the device and install them. /// /// The device on which to install the application. - /// A which represents the base APK to install. /// s which represents the split APKs to install. + /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install-create. - void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, params string[] arguments); + void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress, params string[] arguments); /// /// Like "install", but starts an install session. @@ -359,7 +356,9 @@ public partial interface IAdbClient /// A which represents the application to install. /// The name of the application. /// The session ID of the install session. - void InstallWrite(DeviceData device, Stream apk, string apkName, string session); + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + void InstallWrite(DeviceData device, Stream apk, string apkName, string session, IProgress? progress); /// /// Commit the given active install session, installing the app. @@ -382,146 +381,6 @@ public partial interface IAdbClient /// The device for which to get the list of features supported. /// A list of all features supported by the current device. IEnumerable GetFeatureSet(DeviceData device); - - /// - /// Gets the current device screen snapshot. - /// - /// The device for which to get the screen snapshot. - /// A containing current hierarchy. - /// Failed if start with ERROR or java.lang.Exception. - string DumpScreenString(DeviceData device); - - /// - /// Gets the current device screen snapshot. - /// - /// The device for which to get the screen snapshot. - /// A containing current hierarchy. - XmlDocument? DumpScreen(DeviceData device); - -#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER - /// - /// Gets the current device screen snapshot. - /// - /// The device for which to get the screen snapshot. - /// A containing current hierarchy. - Windows.Data.Xml.Dom.XmlDocument? DumpScreenWinRT(DeviceData device) -#if WINDOWS10_0_17763_0_OR_GREATER - { - Windows.Data.Xml.Dom.XmlDocument doc = new(); - doc.LoadXml(DumpScreen(device)?.OuterXml); - return doc; - } -#else - ; -#endif -#endif - - /// - /// Clicks on the specified coordinates. - /// - /// The device on which to click. - /// The to click. - void Click(DeviceData device, Point cords); - - /// - /// Clicks on the specified coordinates. - /// - /// The device on which to click. - /// The X co-ordinate to click. - /// The Y co-ordinate to click. - void Click(DeviceData device, int x, int y); - - /// - /// Generates a swipe gesture from first element to second element Specify the speed in ms. - /// - /// The device on which to swipe. - /// The start element. - /// The end element. - /// The time spent in swiping. - void Swipe(DeviceData device, Element first, Element second, long speed); - - /// - /// Generates a swipe gesture from co-ordinates x1,y1 to x2,y2 with speed Specify the speed in ms. - /// - /// The device on which to swipe. - /// The start X co-ordinate. - /// The start Y co-ordinate. - /// The end X co-ordinate. - /// The end Y co-ordinate. - /// The time spent in swiping. - void Swipe(DeviceData device, int x1, int y1, int x2, int y2, long speed); - - /// - /// Check if the app is running in foreground. - /// - /// The device on which to check. - /// The package name of the app to check. - /// if the app is running in foreground; otherwise, . - bool IsAppInForeground(DeviceData device, string packageName); - - /// - /// Check if the app is running in background. - /// - /// The device on which to check. - /// The package name of the app to check. - /// if the app is running in background; otherwise, . - bool IsAppRunning(DeviceData device, string packageName); - - /// - /// Get the of the app. - /// - /// The device on which to get status. - /// The package name of the app to check. - /// The of the app. Foreground, stopped or running in background. - AppStatus GetAppStatus(DeviceData device, string packageName); - - /// - /// Get element by xpath. You can specify the waiting time in timeout. - /// - /// The device on which to get element. - /// The xpath of the element. - /// The timeout for waiting the element. - /// Only check once if or . - /// The of . - Element? FindElement(DeviceData device, string xpath, TimeSpan timeout = default); - - /// - /// Get elements by xpath. You can specify the waiting time in timeout. - /// - /// The device on which to get elements. - /// The xpath of the elements. - /// The timeout for waiting the elements. - /// Only check once if or . - /// The of has got. - IEnumerable FindElements(DeviceData device, string xpath, TimeSpan timeout = default); - - /// - /// Send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. - /// - /// The device on which to send key event. - /// The key event to send. - void SendKeyEvent(DeviceData device, string key); - - /// - /// Send text to device. Doesn't support Russian. - /// - /// The device on which to send text. - /// The text to send. - void SendText(DeviceData device, string text); - - /// - /// Start an Android application on device. - /// - /// The device on which to start an application. - /// The package name of the application to start. - void StartApp(DeviceData device, string packageName); - - /// - /// Stop an Android application on device. - /// - /// The device on which to stop an application. - /// The package name of the application to stop. - void StopApp(DeviceData device, string packageName); } /// diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs index c8495c71..c6dd2b88 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs @@ -11,18 +11,26 @@ namespace AdvancedSharpAdbClient public partial interface IAdbCommandLineClient { /// - /// Queries adb for its version number and checks it against . + /// Asynchronously queries adb for its version number and checks it against . /// /// A which can be used to cancel the asynchronous operation. /// A which return a object that contains the version number of the Android Command Line client. Task GetVersionAsync(CancellationToken cancellationToken); /// - /// Starts the adb server by running the adb start-server command. + /// Asynchronously starts the adb server by running the adb start-server command. /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. Task StartServerAsync(CancellationToken cancellationToken); + + /// + /// Determines whether the adb.exe file exists. + /// + /// The path to validate. + /// A which can be used to cancel the asynchronous operation. + /// A which return if the adb.exe file is exists, otherwise . + Task CheckFileExistsAsync(string adbPath, CancellationToken cancellationToken); } } #endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs index f5eaffc0..a04102bb 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs @@ -25,6 +25,7 @@ public partial interface IAdbCommandLineClient /// Determines whether the adb.exe file exists. /// /// The path to validate. + /// if the adb.exe file is exists, otherwise . bool CheckFileExists(string adbPath); } } diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs index 49fa3840..2d908bf6 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs @@ -10,7 +10,7 @@ namespace AdvancedSharpAdbClient public partial interface IAdbServer { /// - /// Starts the adb server if it was not previously running. + /// Asynchronously starts the adb server if it was not previously running. /// /// The path to the adb.exe executable that can be used to start the adb server. /// If this path is not provided, this method will throw an exception if the server @@ -47,7 +47,7 @@ public partial interface IAdbServer Task StartServerAsync(string adbPath, bool restartServerIfNewer, CancellationToken cancellationToken); /// - /// Restarts the adb server if it suddenly became unavailable. Call this class if, for example, + /// Asynchronously restarts the adb server if it suddenly became unavailable. Call this class if, for example, /// you receive an with the flag /// set to - a clear indicating the ADB server died. /// @@ -58,7 +58,7 @@ public partial interface IAdbServer Task RestartServerAsync(CancellationToken cancellationToken); /// - /// Restarts the adb server with new adb path if it suddenly became unavailable. Call this class if, for example, + /// Asynchronously restarts the adb server with new adb path if it suddenly became unavailable. Call this class if, for example, /// you receive an with the flag /// set to - a clear indicating the ADB server died. /// @@ -72,12 +72,13 @@ public partial interface IAdbServer Task RestartServerAsync(string adbPath, CancellationToken cancellationToken); /// - /// Stop the adb server asynchronously. + /// Asynchronously stop the adb server asynchronously. /// + /// A which represents the asynchronous operation. Task StopServerAsync(CancellationToken cancellationToken); /// - /// Gets the status of the adb server asynchronously. + /// Asynchronously gets the status of the adb server asynchronously. /// /// A which can be used to cancel the asynchronous operation. /// A which return a object that describes the status of the adb server. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs index 27b8165e..e9082641 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs @@ -13,7 +13,7 @@ namespace AdvancedSharpAdbClient public partial interface IAdbSocket { /// - /// Reconnects the to the same endpoint it was initially connected to. + /// Asynchronously reconnects the to the same endpoint it was initially connected to. /// Use this when the socket was disconnected by adb and you have restarted adb. /// /// Force reconnect whatever the socket is connected or not. @@ -22,7 +22,7 @@ public partial interface IAdbSocket Task ReconnectAsync(bool isForce, CancellationToken cancellationToken); /// - /// Sends the specified number of bytes of data to a , + /// Asynchronously sends the specified number of bytes of data to a , /// /// A array that acts as a buffer, containing the data to send. /// A that can be used to cancel the task. @@ -30,7 +30,7 @@ public partial interface IAdbSocket Task SendAsync(byte[] data, CancellationToken cancellationToken); /// - /// Sends the specified number of bytes of data to a , + /// Asynchronously sends the specified number of bytes of data to a , /// /// A array that acts as a buffer, containing the data to send. /// A that can be used to cancel the task. @@ -39,7 +39,7 @@ public partial interface IAdbSocket Task SendAsync(byte[] data, int length, CancellationToken cancellationToken); /// - /// Sends the specified number of bytes of data to a , + /// Asynchronously sends the specified number of bytes of data to a , /// /// A array that acts as a buffer, containing the data to send. /// The index of the first byte in the array to send. @@ -49,7 +49,7 @@ public partial interface IAdbSocket Task SendAsync(byte[] data, int offset, int length, CancellationToken cancellationToken); /// - /// Sends a sync request to the device. + /// Asynchronously sends a sync request to the device. /// /// The command to send. /// The path of the file on which the command should operate. @@ -59,7 +59,7 @@ public partial interface IAdbSocket Task SendSyncRequestAsync(SyncCommand command, string path, int permissions, CancellationToken cancellationToken); /// - /// Sends a sync request to the device. + /// Asynchronously sends a sync request to the device. /// /// The command to send. /// The path of the file on which the command should operate. @@ -68,7 +68,7 @@ public partial interface IAdbSocket Task SendSyncRequestAsync(SyncCommand command, string path, CancellationToken cancellationToken); /// - /// Sends a sync request to the device. + /// Asynchronously sends a sync request to the device. /// /// The command to send. /// The length of the data packet that follows. @@ -86,7 +86,7 @@ public partial interface IAdbSocket Task SendAdbRequestAsync(string request, CancellationToken cancellationToken); /// - /// Reads a from an instance when + /// Asynchronously reads a from an instance when /// the connection is in sync mode. /// /// The buffer to store the read data into. @@ -95,7 +95,7 @@ public partial interface IAdbSocket Task ReadAsync(byte[] data, CancellationToken cancellationToken); /// - /// Receives data from a into a receive buffer. + /// Asynchronously receives data from a into a receive buffer. /// /// An array of type that is the storage location for the received data. /// The number of bytes to receive. @@ -112,7 +112,7 @@ public partial interface IAdbSocket Task ReadStringAsync(CancellationToken cancellationToken); /// - /// Reads a from an instance when + /// Asynchronously reads a from an instance when /// the connection is in sync mode. /// /// A that can be used to cancel the task. @@ -120,7 +120,7 @@ public partial interface IAdbSocket Task ReadSyncStringAsync(CancellationToken cancellationToken); /// - /// Reads the response to a sync command. + /// Asynchronously reads the response to a sync command. /// /// A that can be used to cancel the task. /// A that represents the asynchronous operation. The return value of the task is the response that was sent by the device. @@ -136,7 +136,7 @@ public partial interface IAdbSocket #if HAS_BUFFERS /// - /// Sends the specified number of bytes of data to a , + /// Asynchronously sends the specified number of bytes of data to a , /// /// A array that acts as a buffer, containing the data to send. /// A that can be used to cancel the task. @@ -144,7 +144,7 @@ public partial interface IAdbSocket public ValueTask SendAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default) => new(SendAsync(data.ToArray(), cancellationToken)); /// - /// Receives data from a into a receive buffer. + /// Asynchronously receives data from a into a receive buffer. /// /// An array of type Byte that is the storage location for the received data. /// A that can be used to cancel the task. @@ -166,7 +166,7 @@ public ValueTask ReadAsync(Memory data, CancellationToken cancellatio #endif /// - /// Ask to switch the connection to the device/emulator identified by + /// Asynchronously ask to switch the connection to the device/emulator identified by /// . After this request, every client request will /// be sent directly to the adbd daemon running on the device. /// diff --git a/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs b/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs index dfbe9b64..90dc5d74 100644 --- a/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs @@ -10,14 +10,14 @@ namespace AdvancedSharpAdbClient public partial interface IDeviceMonitor { /// - /// Starts the monitoring. + /// Asynchronously starts the monitoring. /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. Task StartAsync(CancellationToken cancellationToken); /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting + /// Asynchronously performs application-defined tasks associated with freeing, releasing, or resetting /// unmanaged resources asynchronously. /// /// A that represents the asynchronous dispose operation. diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index 221bffa5..f2ad8da4 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -14,7 +14,7 @@ namespace AdvancedSharpAdbClient public partial interface ISyncService { /// - /// Pushes (uploads) a file to the remote device. + /// Asynchronously pushes (uploads) a file to the remote device. /// /// A that contains the contents of the file. /// The path, on the device, to which to push the file. @@ -26,7 +26,7 @@ public partial interface ISyncService Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress, CancellationToken cancellationToken); /// - /// Pulls (downloads) a file from the remote device. + /// Asynchronously pulls (downloads) a file from the remote device. /// /// The path, on the device, of the file to pull. /// A that will receive the contents of the file. @@ -36,7 +36,7 @@ public partial interface ISyncService Task PullAsync(string remotePath, Stream stream, IProgress? progress, CancellationToken cancellationToken); /// - /// Returns information about a file on the device. + /// Asynchronously returns information about a file on the device. /// /// The path of the file on the device. /// A that can be used to cancel the task. @@ -44,7 +44,7 @@ public partial interface ISyncService Task StatAsync(string remotePath, CancellationToken cancellationToken); /// - /// Lists the contents of a directory on the device. + /// Asynchronously lists the contents of a directory on the device. /// /// The path to the directory on the device. /// A that can be used to cancel the task. @@ -53,7 +53,7 @@ public partial interface ISyncService #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER /// - /// Lists the contents of a directory on the device. + /// Asynchronously lists the contents of a directory on the device. /// /// The path to the directory on the device. /// A that can be used to cancel the task. @@ -68,14 +68,14 @@ async IAsyncEnumerable GetDirectoryAsyncListing(string remotePat #endif /// - /// Opens this connection. + /// Asynchronously opens this connection. /// /// A that can be used to cancel the task. /// A which represents the asynchronous operation. Task OpenAsync(CancellationToken cancellationToken); /// - /// Reopen this connection. Use this when the socket was disconnected by adb and you have restarted adb. + /// Asynchronously reopen this connection. Use this when the socket was disconnected by adb and you have restarted adb. /// /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. diff --git a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs index 71eef16d..451a5f4c 100644 --- a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs @@ -21,7 +21,7 @@ public partial interface ITcpSocket Task ConnectAsync(EndPoint endPoint, CancellationToken cancellationToken); /// - /// Re-establishes the connection to a remote host. Assumes you have resolved the reason that caused the + /// Asynchronously re-establishes the connection to a remote host. Assumes you have resolved the reason that caused the /// socket to disconnect. /// /// Force reconnect whatever the socket is connected or not. @@ -64,7 +64,7 @@ public partial interface ITcpSocket Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); /// - /// Receives the specified number of bytes from a bound + /// Asynchronously receives the specified number of bytes from a bound /// using the specified SocketFlags. /// /// An array of type Byte that is the storage location for received data. @@ -75,7 +75,7 @@ public partial interface ITcpSocket Task ReceiveAsync(byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken); /// - /// Receives the specified number of bytes from a bound + /// Asynchronously receives the specified number of bytes from a bound /// using the specified SocketFlags. /// /// An array of type Byte that is the storage location for received data. @@ -87,7 +87,7 @@ public partial interface ITcpSocket Task ReceiveAsync(byte[] buffer, int size, SocketFlags socketFlags, CancellationToken cancellationToken); /// - /// Receives the specified number of bytes from a bound + /// Asynchronously receives the specified number of bytes from a bound /// into the specified offset position of the receive buffer, using the specified SocketFlags. /// /// An array of type Byte that is the storage location for received data. @@ -111,7 +111,7 @@ public partial interface ITcpSocket public ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) => new(ReceiveAsync(buffer.ToArray(), socketFlags, cancellationToken)); /// - /// Receives the specified number of bytes from a bound + /// Asynchronously receives the specified number of bytes from a bound /// using the specified SocketFlags. /// /// An array of type Byte that is the storage location for received data. diff --git a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs index d95fb309..9d09e5e5 100644 --- a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs +++ b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs @@ -13,7 +13,7 @@ namespace AdvancedSharpAdbClient.Logs public partial class LogReader { /// - /// Reads the next from the stream. + /// Asynchronously reads the next from the stream. /// /// A which can be used to cancel the asynchronous operation. /// A which return a new object. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PackageInstallProgressState.cs b/AdvancedSharpAdbClient/Models/Enums/PackageInstallProgressState.cs similarity index 88% rename from AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PackageInstallProgressState.cs rename to AdvancedSharpAdbClient/Models/Enums/PackageInstallProgressState.cs index 696278b7..85f59bee 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PackageInstallProgressState.cs +++ b/AdvancedSharpAdbClient/Models/Enums/PackageInstallProgressState.cs @@ -2,13 +2,18 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.Models { /// /// Represents the state of the installation. /// public enum PackageInstallProgressState { + /// + /// Preparing the installation. + /// + Preparing, + /// /// Uploading packages to the device. /// diff --git a/AdvancedSharpAdbClient/Models/Enums/UnixFileType.cs b/AdvancedSharpAdbClient/Models/Enums/UnixFileType.cs index fcfabdc7..b2022884 100644 --- a/AdvancedSharpAdbClient/Models/Enums/UnixFileType.cs +++ b/AdvancedSharpAdbClient/Models/Enums/UnixFileType.cs @@ -12,11 +12,6 @@ namespace AdvancedSharpAdbClient.Models [Flags] public enum UnixFileType { - /// - /// The mask that can be used to retrieve the file type from a . - /// - TypeMask = 0x8000, - /// /// The file is a Unix socket. /// @@ -30,7 +25,7 @@ public enum UnixFileType /// /// The file is a regular file. /// - Regular = TypeMask, + Regular = 0x8000, /// /// The file is a block device. @@ -50,6 +45,11 @@ public enum UnixFileType /// /// The file is a first-in first-out queue. /// - FIFO = 0x1000 + FIFO = 0x1000, + + /// + /// The mask that can be used to retrieve the file type from a . + /// + TypeMask = Regular } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/InstallProgressEventArgs.cs b/AdvancedSharpAdbClient/Models/InstallProgress.EventArgs.cs similarity index 66% rename from AdvancedSharpAdbClient/DeviceCommands/Models/InstallProgressEventArgs.cs rename to AdvancedSharpAdbClient/Models/InstallProgress.EventArgs.cs index d003c714..00760cff 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/InstallProgressEventArgs.cs +++ b/AdvancedSharpAdbClient/Models/InstallProgress.EventArgs.cs @@ -1,23 +1,24 @@ -// +// // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // using System; -namespace AdvancedSharpAdbClient.Models.DeviceCommands +namespace AdvancedSharpAdbClient.Models { /// - /// Represents the state of the installation for . + /// Represents the state of apk installation. /// + /// The state of the installation. public class InstallProgressEventArgs(PackageInstallProgressState state) : EventArgs { /// - /// State of the installation. + /// Get the state of the installation. /// public PackageInstallProgressState State { get; } = state; /// - /// Number of packages which is finished operation. + /// Get the number of packages which is finished operation. /// Used only in , /// and /// state. @@ -25,7 +26,7 @@ public class InstallProgressEventArgs(PackageInstallProgressState state) : Event public int PackageFinished { get; init; } /// - /// Number of packages required for this operation. + /// Get the number of packages required for this operation. /// Used only in , /// and /// state. @@ -33,7 +34,7 @@ public class InstallProgressEventArgs(PackageInstallProgressState state) : Event public int PackageRequired { get; init; } /// - /// Upload percentage (from to ) completed. + /// Get the upload percentage (from to ) completed. /// Used only in state. /// public double UploadProgress { get; init; } @@ -42,6 +43,9 @@ public class InstallProgressEventArgs(PackageInstallProgressState state) : Event /// Initializes a new instance of the class. /// Which is used for state. /// + /// The number of packages which is finished operation. + /// The number of packages required for this operation. + /// Get the upload percentage (from to ) completed. public InstallProgressEventArgs(int packageUploaded, int packageRequired, double uploadProgress) : this(PackageInstallProgressState.Uploading) { PackageFinished = packageUploaded; @@ -55,6 +59,9 @@ public InstallProgressEventArgs(int packageUploaded, int packageRequired, double /// and /// state. /// + /// The number of packages which is finished operation. + /// The number of packages required for this operation. + /// The state of the installation. public InstallProgressEventArgs(int packageFinished, int packageRequired, PackageInstallProgressState state) : this(state) { PackageFinished = packageFinished; diff --git a/AdvancedSharpAdbClient/Models/ShellStream.cs b/AdvancedSharpAdbClient/Models/ShellStream.cs index e3a89064..2fe5a3f3 100644 --- a/AdvancedSharpAdbClient/Models/ShellStream.cs +++ b/AdvancedSharpAdbClient/Models/ShellStream.cs @@ -210,7 +210,7 @@ public override int Read(byte[] buffer, int offset, int count) } byte[] miniBuffer = new byte[1]; - int miniRead = Inner.Read(miniBuffer, 1); + int miniRead = Inner.Read(miniBuffer, 0, 1); if (miniRead == 0) { @@ -411,7 +411,7 @@ async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToke continue; } - int miniRead = await Inner.ReadAsync(miniBuffer, 1, cancellationToken).ConfigureAwait(false); + int miniRead = await Inner.ReadAsync(miniBuffer, 0, 1, cancellationToken).ConfigureAwait(false); if (miniRead == 0) { @@ -431,7 +431,7 @@ async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToke // we need to read one more byte from the inner stream. if (read > 0 && buffer[offset + read - 1] == 0x0d) { - _ = await Inner.ReadAsync(miniBuffer, 1, cancellationToken).ConfigureAwait(false); + _ = await Inner.ReadAsync(miniBuffer, 0, 1, cancellationToken).ConfigureAwait(false); int nextByte = miniBuffer[0]; if (nextByte == 0x0a) diff --git a/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs b/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs index 8942242b..fe6df0b8 100644 --- a/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs +++ b/AdvancedSharpAdbClient/Models/SyncService.EventArgs.cs @@ -26,6 +26,6 @@ public class SyncProgressChangedEventArgs(long current, long total) : EventArgs /// /// Gets the number of progress percentage (from to ) for the sync operation. /// - public double ProgressPercentage => TotalBytesToReceive == 0L ? 0.0 : ReceivedBytesSize * 100.0 / TotalBytesToReceive; + public double ProgressPercentage => TotalBytesToReceive == 0 ? 0 : ReceivedBytesSize * 100d / TotalBytesToReceive; } } diff --git a/AdvancedSharpAdbClient/Polyfills/DnsEndPoint.cs b/AdvancedSharpAdbClient/Polyfills/DnsEndPoint.cs index 295846e9..0c9e9b13 100644 --- a/AdvancedSharpAdbClient/Polyfills/DnsEndPoint.cs +++ b/AdvancedSharpAdbClient/Polyfills/DnsEndPoint.cs @@ -1,5 +1,6 @@ #if NETFRAMEWORK && !NET40_OR_GREATER using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; @@ -9,6 +10,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Represents a network endpoint as a host name or a string representation of an IP address and a port number. /// + [EditorBrowsable(EditorBrowsableState.Never)] public class DnsEndPoint : EndPoint { private readonly AddressFamily _family; diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/DateTimeExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/DateTimeExtensions.cs index 33e0d8a5..b3d26794 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/DateTimeExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/DateTimeExtensions.cs @@ -3,12 +3,14 @@ // using System; +using System.ComponentModel; namespace AdvancedSharpAdbClient.Polyfills { /// /// Provides helper methods for working with Unix-based date formats. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class DateTimeExtensions { #if NETFRAMEWORK && !NET46_OR_GREATER diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumExtensions.cs index e9718122..15a423b5 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumExtensions.cs @@ -3,12 +3,14 @@ // using System; +using System.ComponentModel; namespace AdvancedSharpAdbClient.Polyfills { /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class EnumExtensions { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs index 4ee7fabf..302de322 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; namespace AdvancedSharpAdbClient.Polyfills @@ -7,6 +8,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class EnumerableExtensions { /// @@ -44,7 +46,7 @@ public static void AddRange(this ICollection source, IEnumerab #if HAS_TASK /// - /// Creates an array from a . + /// Asynchronously creates an array from a . /// /// The type of the elements of . /// An to create an array from. @@ -53,7 +55,7 @@ public static Task ToArrayAsync(this Task x.Result.ToArray()); /// - /// Creates an array from a . + /// Asynchronously creates an array from a . /// /// The type of the elements of . /// An to create an array from. diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/ExceptionExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/ExceptionExtensions.cs index 41a2f0fa..b4f3a1a4 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/ExceptionExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/ExceptionExtensions.cs @@ -1,4 +1,9 @@ -using System; +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -8,6 +13,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class ExceptionExtensions { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs index 3f6f8cb6..ba5f1b76 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/SocketExtensions.cs @@ -4,6 +4,7 @@ // using System; +using System.ComponentModel; using System.Net.Sockets; using System.Threading; @@ -12,6 +13,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class SocketExtensions { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/StreamExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/StreamExtensions.cs index 57e545a6..f0169bca 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/StreamExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/StreamExtensions.cs @@ -1,8 +1,10 @@ -// +#if NET35 +// // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // using System; +using System.ComponentModel; using System.IO; using System.Threading; @@ -11,118 +13,9 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class StreamExtensions { - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// The stream from which to read data. - /// An array of bytes. When this method returns, the contents of this region are replaced by the bytes read from the current source. - /// The total number of bytes read into the buffer. This can be less than the size of the buffer if that many bytes are not currently available, - /// or zero (0) if the buffer's length is zero or the end of the stream has been reached. - public static int Read(this Stream stream, byte[] buffer) => -#if HAS_BUFFERS - stream.Read(buffer); -#else - stream.Read(buffer, 0, buffer.Length); -#endif - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// The stream from which to read data. - /// An array of bytes. When this method returns, the contents of this region are replaced by the bytes read from the current source. - /// The maximum number of bytes to read. - /// The total number of bytes read into the buffer. This can be less than the size of the buffer if that many bytes are not currently available, - /// or zero (0) if the buffer's length is zero or the end of the stream has been reached. - public static int Read(this Stream stream, byte[] buffer, int count) => - stream.Read(buffer, 0, count); - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// The stream from which to read data. - /// An array of bytes. This method copies the contents of this region to the current stream. - public static void Write(this Stream stream, byte[] buffer) => -#if HAS_BUFFERS - stream.Write(buffer); -#else - stream.Write(buffer, 0, buffer.Length); -#endif - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// The stream from which to read data. - /// An array of bytes. This method copies bytes from buffer to the current stream. - /// The number of bytes to be written to the current stream. - public static void Write(this Stream stream, byte[] buffer, int count) => - stream.Write(buffer, 0, count); - -#if HAS_TASK - /// - /// Asynchronously reads a sequence of bytes from the current stream, within monitors cancellation requests. - /// - /// The stream from which to read data. - /// The buffer to write the data into. - /// The token to monitor for cancellation requests. The default value is . - /// A task that represents the asynchronous read operation. The value of the TResult parameter - /// contains the total number of bytes read into the buffer. The result value can be less than the number - /// of bytes requested if the number of bytes currently available is less than the requested number, - /// or it can be 0 (zero) if length of the buffer is 0 or if the end of the stream has been reached. - /// Cancelling the task will also close the stream. - public static Task ReadAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default) => -#if HAS_BUFFERS - stream.ReadAsync(buffer, cancellationToken).AsTask(); -#else - stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); -#endif - - /// - /// Asynchronously reads a sequence of bytes from the current stream, advances the position - /// within the stream by the number of bytes read, and monitors cancellation requests. - /// - /// The stream from which to read data. - /// The buffer to write the data into. - /// The maximum number of bytes to read. - /// The token to monitor for cancellation requests. The default value is . - /// A task that represents the asynchronous read operation. The value of the TResult parameter - /// contains the total number of bytes read into the buffer. The result value can be less than the number - /// of bytes requested if the number of bytes currently available is less than the requested number, - /// or it can be 0 (zero) if is 0 or if the end of the stream has been reached. - /// Cancelling the task will also close the stream. - public static Task ReadAsync(this Stream stream, byte[] buffer, int count, CancellationToken cancellationToken = default) => - stream.ReadAsync(buffer, 0, count, cancellationToken); - - /// - /// Asynchronously writes a sequence of bytes to the current stream, within this stream by the number of bytes written. - /// - /// The stream from which to write data. - /// The buffer to write data from. - /// The token to monitor for cancellation requests. The default value is . - /// A task that represents the asynchronous write operation. - /// Cancelling the task will also close the stream. - public static Task WriteAsync(this Stream stream, byte[] buffer, CancellationToken cancellationToken = default) => -#if HAS_BUFFERS - stream.WriteAsync(buffer, cancellationToken).AsTask(); -#else - stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken); -#endif - - /// - /// Asynchronously writes a sequence of bytes to the current stream, advances the current position - /// within this stream by the number of bytes written, and monitors cancellation requests. - /// - /// The stream from which to write data. - /// The buffer to write data from. - /// The maximum number of bytes to write. - /// The token to monitor for cancellation requests. The default value is . - /// A task that represents the asynchronous write operation. - /// Cancelling the task will also close the stream. - public static Task WriteAsync(this Stream stream, byte[] buffer, int count, CancellationToken cancellationToken = default) => - stream.WriteAsync(buffer, 0, count, cancellationToken); - -#if NET35 /// /// Asynchronously reads a sequence of bytes from the current stream, advances the position /// within the stream by the number of bytes read, and monitors cancellation requests. @@ -222,7 +115,6 @@ public static Task WriteAsync(this Stream stream, byte[] buffer, int offset, int return taskCompletionSource.Task; } -#endif -#endif } } +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/StringBuilderExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/StringBuilderExtensions.cs index d3480120..4dbcb829 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/StringBuilderExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/StringBuilderExtensions.cs @@ -3,6 +3,7 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // +using System.ComponentModel; using System.Text; namespace AdvancedSharpAdbClient.Polyfills @@ -10,6 +11,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class StringBuilderExtensions { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/StringExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/StringExtensions.cs index 1b1db8ba..94967f16 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/StringExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/StringExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Text; namespace AdvancedSharpAdbClient.Polyfills @@ -11,6 +12,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Provides extension methods for the class. /// + [EditorBrowsable(EditorBrowsableState.Never)] public static class StringExtensions { /// diff --git a/AdvancedSharpAdbClient/Polyfills/HashCode.cs b/AdvancedSharpAdbClient/Polyfills/HashCode.cs index a88fa196..50eeeb3c 100644 --- a/AdvancedSharpAdbClient/Polyfills/HashCode.cs +++ b/AdvancedSharpAdbClient/Polyfills/HashCode.cs @@ -55,6 +55,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Combines the hash code for multiple values into a single hash code. /// + [EditorBrowsable(EditorBrowsableState.Never)] public struct HashCode { private static readonly uint s_seed = GenerateGlobalSeed(); diff --git a/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs b/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs index 5c88a752..e92586af 100644 --- a/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs +++ b/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs @@ -2,10 +2,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; + namespace AdvancedSharpAdbClient.Polyfills { /// Defines a provider for progress updates. /// The type of progress update value. + [EditorBrowsable(EditorBrowsableState.Never)] public interface IProgress { /// Reports a progress update. diff --git a/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyCollection.cs b/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyCollection.cs index cb3ca93c..922a36bd 100644 --- a/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyCollection.cs +++ b/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyCollection.cs @@ -3,6 +3,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.ComponentModel; namespace AdvancedSharpAdbClient.Polyfills { @@ -10,6 +11,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// Represents a strongly-typed, read-only collection of elements. /// /// The type of the elements. + [EditorBrowsable(EditorBrowsableState.Never)] public interface IReadOnlyCollection : IEnumerable { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyList.cs b/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyList.cs index 07822794..3abef338 100644 --- a/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyList.cs +++ b/AdvancedSharpAdbClient/Polyfills/Interfaces/IReadOnlyList.cs @@ -2,12 +2,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.ComponentModel; + namespace AdvancedSharpAdbClient.Polyfills { /// /// Represents a read-only collection of elements that can be accessed by index. /// /// The type of elements in the read-only list. + [EditorBrowsable(EditorBrowsableState.Never)] public interface IReadOnlyList : IReadOnlyCollection { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Point.cs b/AdvancedSharpAdbClient/Polyfills/Point.cs index 9f6ac9e0..7e710288 100644 --- a/AdvancedSharpAdbClient/Polyfills/Point.cs +++ b/AdvancedSharpAdbClient/Polyfills/Point.cs @@ -3,6 +3,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; namespace AdvancedSharpAdbClient.Polyfills @@ -10,6 +11,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// /// Represents an ordered pair of x and y coordinates that define a point in a two-dimensional plane. /// + [EditorBrowsable(EditorBrowsableState.Never)] public struct Point : IEquatable { /// diff --git a/AdvancedSharpAdbClient/Polyfills/Rectangle.cs b/AdvancedSharpAdbClient/Polyfills/Rectangle.cs index 520e97a4..e721c715 100644 --- a/AdvancedSharpAdbClient/Polyfills/Rectangle.cs +++ b/AdvancedSharpAdbClient/Polyfills/Rectangle.cs @@ -3,6 +3,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; namespace AdvancedSharpAdbClient.Polyfills @@ -14,6 +15,7 @@ namespace AdvancedSharpAdbClient.Polyfills /// The y-coordinate of the upper-left corner of the rectangle. /// The width of the rectangle. /// The height of the rectangle. + [EditorBrowsable(EditorBrowsableState.Never)] public struct Rectangle(int x, int y, int width, int height) : IEquatable { /// diff --git a/AdvancedSharpAdbClient/Properties/GlobalUsings.cs b/AdvancedSharpAdbClient/Properties/GlobalUsings.cs index c4d2f724..05610a52 100644 --- a/AdvancedSharpAdbClient/Properties/GlobalUsings.cs +++ b/AdvancedSharpAdbClient/Properties/GlobalUsings.cs @@ -1,12 +1,11 @@ #region AdvancedSharpAdbClient -global using AdvancedSharpAdbClient.DeviceCommands; +global using AdvancedSharpAdbClient.DeviceCommands.Models; +global using AdvancedSharpAdbClient.DeviceCommands.Receivers; global using AdvancedSharpAdbClient.Exceptions; global using AdvancedSharpAdbClient.Logs; global using AdvancedSharpAdbClient.Models; -global using AdvancedSharpAdbClient.Models.DeviceCommands; global using AdvancedSharpAdbClient.Polyfills; global using AdvancedSharpAdbClient.Receivers; -global using AdvancedSharpAdbClient.Receivers.DeviceCommands; #endregion #if NET @@ -21,6 +20,7 @@ global using Windows.Foundation; global using Windows.Foundation.Metadata; global using Windows.Graphics.Imaging; +global using Windows.Storage; global using Windows.Storage.Streams; global using Windows.System; global using Windows.UI.Core; diff --git a/AdvancedSharpAdbClient/Receivers/ConsoleOutputReceiver.cs b/AdvancedSharpAdbClient/Receivers/ConsoleOutputReceiver.cs index 8d857f41..250efec1 100644 --- a/AdvancedSharpAdbClient/Receivers/ConsoleOutputReceiver.cs +++ b/AdvancedSharpAdbClient/Receivers/ConsoleOutputReceiver.cs @@ -35,15 +35,12 @@ public partial class ConsoleOutputReceiver(ILogger? logge /// Gets a that represents the current . /// /// A that represents the current . - public override string ToString() => output.ToString(); + public override string ToString() => TrimLines ? output.ToString().Trim() : output.ToString(); - /// - /// Throws an error message if the console output line contains an error message. - /// - /// The line to inspect. - public virtual void ThrowOnError(string line) + /// + protected override void ThrowOnError(string line) { - if (!ParsesErrors) + if (ParsesErrors && !line.StartsWith('#') && !line.StartsWith('$')) { if (line.EndsWith(": not found")) { diff --git a/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs b/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs index bd0f16a4..c6b1bca3 100644 --- a/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs +++ b/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs @@ -14,17 +14,11 @@ namespace AdvancedSharpAdbClient.Receivers public interface IShellOutputReceiver { /// - /// Gets a value indicating whether the receiver parses error messages. + /// Adds a line to the output. /// - /// if this receiver parsers error messages; otherwise . - /// The default value is . If set to , the - /// will detect common error messages and throw an exception. - bool ParsesErrors { get; } - - /// - /// Adds the output. - /// - void AddOutput(string line); + /// The line to add to the output. + /// if continue receive messages; otherwise . + bool AddOutput(string line); /// /// Flushes the output. diff --git a/AdvancedSharpAdbClient/Receivers/MultilineReceiver.cs b/AdvancedSharpAdbClient/Receivers/MultilineReceiver.cs index f98c2765..8cd88263 100644 --- a/AdvancedSharpAdbClient/Receivers/MultilineReceiver.cs +++ b/AdvancedSharpAdbClient/Receivers/MultilineReceiver.cs @@ -9,43 +9,43 @@ namespace AdvancedSharpAdbClient.Receivers /// /// A multiline receiver to receive and process shell output with multi lines. /// - public abstract class MultiLineReceiver : IShellOutputReceiver + public abstract class MultiLineReceiver : ShellOutputReceiver { /// /// Initializes a new instance of the class. /// - public MultiLineReceiver() => Lines = new List(); + public MultiLineReceiver() { } /// /// Gets or sets a value indicating whether [trim lines]. /// /// if [trim lines]; otherwise, . - public bool TrimLines { get; set; } + public bool TrimLines { get; set; } = false; /// /// Gets or sets a value indicating whether the receiver parses error messages. /// /// if this receiver parsers error messages; otherwise . - /// The default value is . If set to , the + /// The default value is . If set to , the /// will detect common error messages and throw an exception. - public virtual bool ParsesErrors { get; protected set; } + public virtual bool ParsesErrors { get; init; } = true; /// /// Gets or sets the lines. /// /// The lines. - protected ICollection Lines { get; set; } + protected List Lines { get; set; } = []; - /// - /// Adds a line to the output. - /// - /// The line to add to the output. - public virtual void AddOutput(string line) => Lines.Add(line); + /// + public override bool AddOutput(string line) + { + ThrowOnError(line); + Lines.Add(TrimLines ? line.Trim() : line); + return true; + } - /// - /// Flushes the output. - /// - public virtual void Flush() + /// + public override void Flush() { if (Lines.Count > 0) { @@ -53,14 +53,14 @@ public virtual void Flush() ProcessNewLines(Lines); Lines.Clear(); } - Done(); } /// - /// Finishes the receiver. + /// Throws an error message if the console output line contains an error message. /// - protected virtual void Done() + /// The line to inspect. + protected virtual void ThrowOnError(string line) { // Do nothing } diff --git a/AdvancedSharpAdbClient/Receivers/ShellOutputReceiver.cs b/AdvancedSharpAdbClient/Receivers/ShellOutputReceiver.cs new file mode 100644 index 00000000..5ee94d07 --- /dev/null +++ b/AdvancedSharpAdbClient/Receivers/ShellOutputReceiver.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +namespace AdvancedSharpAdbClient.Receivers +{ + /// + /// A multiline receiver to receive and process shell output with multi lines. + /// + public abstract class ShellOutputReceiver : IShellOutputReceiver + { + /// + /// Initializes a new instance of the class. + /// + public ShellOutputReceiver() { } + + /// + public abstract bool AddOutput(string line); + + /// + public virtual void Flush() => Done(); + + /// + /// Finishes the receiver. + /// + protected virtual void Done() + { + // Do nothing + } + } +} diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 27677b60..856906da 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Runtime.CompilerServices; using System.Threading; @@ -186,7 +185,7 @@ public virtual async Task PullAsync(string remoteFilePath, Stream stream, IProgr await stream.WriteAsync(buffer.AsMemory(0, size), cancellationToken).ConfigureAwait(false); #else await Socket.ReadAsync(buffer, size, cancellationToken).ConfigureAwait(false); - await stream.WriteAsync(buffer, size, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(buffer, 0, size, cancellationToken).ConfigureAwait(false); #endif totalBytesRead += size; @@ -307,7 +306,7 @@ public virtual async IAsyncEnumerable GetDirectoryAsyncListing(s #endif /// - /// Reads the statistics of a file from the socket. + /// Asynchronously reads the statistics of a file from the socket. /// /// A that can be used to cancel the task. /// A which return a object that contains information about the file. diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index 06465a80..38f7a119 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -273,7 +273,7 @@ public virtual void Pull(string remoteFilePath, Stream stream, IProgress - /// Re-establishes the connection to a remote host. Assumes you have resolved the reason that caused the + /// Asynchronously re-establishes the connection to a remote host. Assumes you have resolved the reason that caused the /// socket to disconnect. /// /// A which can be used to cancel the asynchronous task. diff --git a/README.md b/README.md index bcf64b1a..2c07d7e1 100644 --- a/README.md +++ b/README.md @@ -60,15 +60,15 @@ If you want to automate 2 or more devices at the same time, you must remember: 1 You can look at the examples to understand more ```cs -static AdbClient client; - -static DeviceData device; +static AdbClient adbClient; +static DeviceData deviceData; static void Main(string[] args) { - client = new AdbClient(); - client.Connect("127.0.0.1:62001"); - device = client.GetDevices().FirstOrDefault(); // Get first connected device + adbClient = new AdbClient(); + adbClient.Connect("127.0.0.1:62001"); + device = adbClient.GetDevices().FirstOrDefault(); // Get first connected device + ... } ``` @@ -78,29 +78,27 @@ static void Main(string[] args) You can find the element on the screen by xpath ```cs -static AdbClient client; - -static DeviceData device; +static DeviceClient deviceClient; static void Main(string[] args) { - client = new AdbClient(); - client.Connect("127.0.0.1:62001"); - device = client.GetDevices().FirstOrDefault(); - Element element = client.FindElement(device, "//node[@text='Login']"); + ... + deviceClient = new DeviceClient(adbClient, deviceData); + Element element = deviceClient.FindElement("//node[@text='Login']"); + ... } ``` You can also specify the waiting time for the element ```cs -Element element = client.FindElement(device, "//node[@text='Login']", TimeSpan.FromSeconds(5)); +Element element = deviceClient.FindElement("//node[@text='Login']", TimeSpan.FromSeconds(5)); ``` And you can find several elements ```cs -Element[] element = client.FindElements(device, "//node[@resource-id='Login']", TimeSpan.FromSeconds(5)); +Element[] element = deviceClient.FindElements("//node[@resource-id='Login']", TimeSpan.FromSeconds(5)); ``` ### Getting element attributes @@ -110,7 +108,7 @@ You can get all element attributes static void Main(string[] args) { ... - Element element = client.FindElement(device, "//node[@resource-id='Login']", TimeSpan.FromSeconds(3)); + Element element = deviceClient.FindElement("//node[@resource-id='Login']", TimeSpan.FromSeconds(3)); string eltext = element.Attributes["text"]; string bounds = element.Attributes["bounds"]; ... @@ -125,7 +123,7 @@ You can click on the x and y coordinates static void Main(string[] args) { ... - client.Click(device, 600, 600); // Click on the coordinates (600;600) + deviceClient.Click(600, 600); // Click on the coordinates (600;600) ... } ``` @@ -136,7 +134,7 @@ Or on the element(need xpath) static void Main(string[] args) { ... - Element element = client.FindElement(device, "//node[@text='Login']", TimeSpan.FromSeconds(3)); + Element element = deviceClient.FindElement("//node[@text='Login']", TimeSpan.FromSeconds(3)); element.Click(); // Click on element by xpath //node[@text='Login'] ... } @@ -163,9 +161,9 @@ You can swipe from one element to another static void Main(string[] args) { ... - Element first = client.FindElement(device, "//node[@text='Login']"); - Element second = client.FindElement(device, "//node[@text='Password']"); - client.Swipe(device, first, second, 100); // Swipe 100 ms + Element first = deviceClient.FindElement("//node[@text='Login']"); + Element second = deviceClient.FindElement("//node[@text='Password']"); + deviceClient.Swipe(first, second, 100); // Swipe 100 ms ... } ``` @@ -176,8 +174,7 @@ Or swipe by coordinates static void Main(string[] args) { ... - device = client.GetDevices().FirstOrDefault(); - client.Swipe(device, 600, 1000, 600, 500, 100); // Swipe from (600;1000) to (600;500) on 100 ms + deviceClient.Swipe(600, 1000, 600, 500, 100); // Swipe from (600;1000) to (600;500) on 100 ms ... } ``` @@ -187,9 +184,9 @@ The Swipe() method throw ElementNotFoundException if the element is not found ```cs try { - client.Swipe(device, 0x2232323, 0x954, 0x9128, 0x11111, 200); + deviceClient.Swipe(0x2232323, 0x954, 0x9128, 0x11111, 200); ... - client.Swipe(device, first, second, 200); + deviceClient.Swipe(first, second, 200); } catch (Exception ex) { @@ -207,7 +204,7 @@ The text field should be in focus static void Main(string[] args) { ... - client.SendText(device, "text"); // Send text to device + deviceClient.SendText("text"); // Send text to device ... } ``` @@ -218,7 +215,7 @@ You can also send text to the element (clicks on the element and sends the text) static void Main(string[] args) { ... - client.FindElement(device, "//node[@resource-id='Login']").SendText("text"); // Send text to the element by xpath //node[@resource-id='Login'] + deviceClient.FindElement("//node[@resource-id='Login']").SendText("text"); // Send text to the element by xpath //node[@resource-id='Login'] ... } ``` @@ -228,7 +225,7 @@ The SendText() method throw InvalidTextException if text is incorrect ```cs try { - client.SendText(device, null); + deviceClient.SendText(null); } catch (Exception ex) { @@ -249,7 +246,7 @@ The text field should be in focus static void Main(string[] args) { ... - client.ClearInput(device, 25); // The second argument is to specify the maximum number of characters to be erased + adbClient.ClearInput(deviceData, 25); // The second argument is to specify the maximum number of characters to be erased ... } ``` @@ -260,7 +257,7 @@ static void Main(string[] args) static void Main(string[] args) { ... - client.FindElement(device, "//node[@resource-id='Login']").ClearInput(); // Get element text attribute and remove text length symbols + deviceClient.FindElement("//node[@resource-id='Login']").ClearInput(); // Get element text attribute and remove text length symbols ... } ``` @@ -273,7 +270,7 @@ You can see key events here https://developer.android.com/reference/android/view static void Main(string[] args) { ... - client.SendKeyEvent(device, "KEYCODE_TAB"); + deviceClient.SendKeyEvent("KEYCODE_TAB"); ... } ``` @@ -283,7 +280,7 @@ The SendKeyEvent method throw InvalidKeyEventException if key event is incorrect ```cs try { - client.SendKeyEvent(device, null); + deviceClient.SendKeyEvent(null); } catch (Exception ex) { @@ -297,9 +294,9 @@ catch (Exception ex) static void Main(string[] args) { ... - client.ClickBackButton(device); // Click Back button + adbClient.ClickBackButton(device); // Click Back button ... - client.ClickHomeButton(device); // Click Home button + adbClient.ClickHomeButton(device); // Click Home button ... } ``` @@ -312,7 +309,7 @@ static void Main(string[] args) static void Main(string[] args) { ... - PackageManager manager = new PackageManager(client, device); + PackageManager manager = new PackageManager(adbClient, deviceData); manager.InstallPackage(@"C:\Users\me\Documents\mypackage.apk"); manager.UninstallPackage("com.android.app"); ... @@ -327,8 +324,8 @@ static void Main(string[] args) ... using (FileStream stream = File.OpenRead("Application.apk")) { - client.Install(device, stream); - client.Uninstall(device, "com.android.app"); + adbClient.Install(device, stream); + adbClient.Uninstall(device, "com.android.app"); } ... } @@ -339,7 +336,7 @@ static void Main(string[] args) static void Main(string[] args) { ... - PackageManager manager = new PackageManager(client, device); + PackageManager manager = new PackageManager(adbClient, deviceData); manager.InstallMultiplePackage(@"C:\Users\me\Documents\base.apk", new[] { @"C:\Users\me\Documents\split_1.apk", @"C:\Users\me\Documents\split_2.apk" }); // Install split app whith base app manager.InstallMultiplePackage(new[] { @"C:\Users\me\Documents\split_3.apk", @"C:\Users\me\Documents\split_4.apk" }, "com.android.app"); // Add split app to base app which packagename is 'com.android.app' ... @@ -352,8 +349,8 @@ Or you can use AdbClient.InstallMultiple static void Main(string[] args) { ... - client.InstallMultiple(device, File.OpenRead("base.apk"), new[] { File.OpenRead("split_1.apk"), File.OpenRead("split_2.apk") }); // Install split app whith base app - client.InstallMultiple(device, new[] { File.OpenRead("split_3.apk"), File.OpenRead("split_4.apk") }, "com.android.app"); // Add split app to base app which packagename is 'com.android.app' + adbClient.InstallMultiple(device, File.OpenRead("base.apk"), new[] { File.OpenRead("split_1.apk"), File.OpenRead("split_2.apk") }); // Install split app whith base app + adbClient.InstallMultiple(device, new[] { File.OpenRead("split_3.apk"), File.OpenRead("split_4.apk") }, "com.android.app"); // Add split app to base app which packagename is 'com.android.app' ... } ``` @@ -364,8 +361,8 @@ static void Main(string[] args) static void Main(string[] args) { ... - client.StartApp(device, "com.android.app"); - client.StopApp(device, "com.android.app"); // force-stop + deviceClient.StartApp("com.android.app"); + deviceClient.StopApp("com.android.app"); // force-stop ... } ``` @@ -376,9 +373,9 @@ static void Main(string[] args) static async void Main(string[] args) { ... - Image img = client.GetFrameBuffer(device, CancellationToken.None); // synchronously + Image img = adbClient.GetFrameBuffer(deviceData, CancellationToken.None); // synchronously ... - Image img = await client.GetFrameBufferAsync(device, CancellationToken.None); // asynchronously + Image img = await adbClient.GetFrameBufferAsync(deviceData, CancellationToken.None); // asynchronously ... } ``` @@ -389,7 +386,7 @@ static async void Main(string[] args) static void Main(string[] args) { ... - XmlDocument screen = client.DumpScreen(device); + XmlDocument screen = deviceClient.DumpScreen(); ... } ``` @@ -399,7 +396,7 @@ static void Main(string[] args) ```cs void DownloadFile() { - using (SyncService service = new SyncService(new AdbSocket(client.EndPoint), device)) + using (SyncService service = new SyncService(deviceData)) { using (FileStream stream = File.OpenWrite(@"C:\MyFile.txt")) { @@ -410,7 +407,7 @@ void DownloadFile() void UploadFile() { - using (SyncService service = new SyncService(new AdbSocket(client.EndPoint), device)) + using (SyncService service = new SyncService(deviceData)) { using (FileStream stream = File.OpenRead(@"C:\MyFile.txt")) { @@ -427,9 +424,9 @@ static async void Main(string[] args) { ... ConsoleOutputReceiver receiver = new ConsoleOutputReceiver(); - client.ExecuteRemoteCommand("echo Hello, World", device, receiver); // synchronously + adbClient.ExecuteRemoteCommand("echo Hello, World", device, receiver); // synchronously ... - await client.ExecuteRemoteCommandAsync("echo Hello, World", device, receiver, CancellationToken.None); // asynchronously + await adbClient.ExecuteRemoteCommandAsync("echo Hello, World", device, receiver, default); // asynchronously ... } ```