diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs new file mode 100644 index 00000000..6efca8f9 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs @@ -0,0 +1,1007 @@ +using AdvancedSharpAdbClient.Exceptions; +using AdvancedSharpAdbClient.Logs; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class AdbClientTests + { + [Fact] + public async void GetAdbVersionAsyncTest() + { + string[] responseMessages = new string[] + { + "0020" + }; + + string[] requests = new string[] + { + "host:version" + }; + + int version = 0; + + await RunTestAsync( + OkResponse, + responseMessages, + requests, + async () => version = await TestClient.GetAdbVersionAsync()); + + // Make sure and the correct value is returned. + Assert.Equal(32, version); + } + + [Fact] + public async void KillAdbAsyncTest() + { + string[] requests = new string[] + { + "host:kill" + }; + + await RunTestAsync( + NoResponses, + NoResponseMessages, + requests, + () => TestClient.KillAdbAsync()); + } + + [Fact] + public async void GetDevicesAsyncTest() + { + string[] responseMessages = new string[] + { + "169.254.109.177:5555 device product:VS Emulator 5\" KitKat (4.4) XXHDPI Phone model:5__KitKat__4_4__XXHDPI_Phone device:donatello\n" + }; + + string[] requests = new string[] + { + "host:devices-l" + }; + + IEnumerable devices = null; + + await RunTestAsync( + OkResponse, + responseMessages, + requests, + async () => devices = await TestClient.GetDevicesAsync()); + + // Make sure and the correct value is returned. + Assert.NotNull(devices); + Assert.Single(devices); + + DeviceData device = devices.Single(); + + Assert.Equal("169.254.109.177:5555", device.Serial); + Assert.Equal(DeviceState.Online, device.State); + Assert.Equal("5__KitKat__4_4__XXHDPI_Phone", device.Model); + Assert.Equal("donatello", device.Name); + } + + [Fact] + public async void CreateForwardAsyncTest() => + await RunCreateForwardAsyncTest( + (device) => TestClient.CreateForwardAsync(device, "tcp:1", "tcp:2", true), + "tcp:1;tcp:2"); + + + [Fact] + public async void CreateReverseAsyncTest() => + await RunCreateReverseAsyncTest( + (device) => TestClient.CreateReverseForwardAsync(device, "tcp:1", "tcp:2", true), + "tcp:1;tcp:2"); + + [Fact] + public async void CreateTcpForwardAsyncTest() => + await RunCreateForwardAsyncTest( + (device) => TestClient.CreateForwardAsync(device, 3, 4), + "tcp:3;tcp:4"); + + [Fact] + public async void CreateSocketForwardAsyncTest() => + await RunCreateForwardAsyncTest( + (device) => TestClient.CreateForwardAsync(device, 5, "/socket/1"), + "tcp:5;local:/socket/1"); + + [Fact] + public async void CreateDuplicateForwardAsyncTest() + { + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.FromError("cannot rebind existing socket") + }; + + string[] requests = new string[] + { + "host-serial:169.254.109.177:5555:forward:norebind:tcp:1;tcp:2" + }; + + _ = await Assert.ThrowsAsync(() => + RunTestAsync( + responses, + NoResponseMessages, + requests, + () => TestClient.CreateForwardAsync(Device, "tcp:1", "tcp:2", false))); + } + + [Fact] + public async void RemoveForwardAsyncTest() + { + string[] requests = new string[] + { + "host-serial:169.254.109.177:5555:killforward:tcp:1" + }; + + await RunTestAsync( + OkResponse, + NoResponseMessages, + requests, + () => TestClient.RemoveForwardAsync(Device, 1)); + } + + [Fact] + public async void RemoveReverseForwardAsyncTest() + { + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "reverse:killforward:localabstract:test" + }; + + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }; + + await RunTestAsync( + responses, + NoResponseMessages, + requests, + () => TestClient.RemoveReverseForwardAsync(Device, "localabstract:test")); + } + + [Fact] + public async void RemoveAllForwardsAsyncTest() + { + string[] requests = new string[] + { + "host-serial:169.254.109.177:5555:killforward-all" + }; + + await RunTestAsync( + OkResponse, + NoResponseMessages, + requests, + () => TestClient.RemoveAllForwardsAsync(Device)); + } + + [Fact] + public async void RemoveAllReversesAsyncTest() + { + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "reverse:killforward-all" + }; + + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }; + + await RunTestAsync( + responses, + NoResponseMessages, + requests, + () => TestClient.RemoveAllReverseForwardsAsync(Device)); + } + + [Fact] + public async void ListForwardAsyncTest() + { + string[] responseMessages = new string[] + { + "169.254.109.177:5555 tcp:1 tcp:2\n169.254.109.177:5555 tcp:3 tcp:4\n169.254.109.177:5555 tcp:5 local:/socket/1\n" + }; + + string[] requests = new string[] + { + "host-serial:169.254.109.177:5555:list-forward" + }; + + ForwardData[] forwards = null; + + await RunTestAsync( + OkResponse, + responseMessages, + requests, + async () => forwards = (await TestClient.ListForwardAsync(Device)).ToArray()); + + Assert.NotNull(forwards); + Assert.Equal(3, forwards.Length); + Assert.Equal("169.254.109.177:5555", forwards[0].SerialNumber); + Assert.Equal("tcp:1", forwards[0].Local); + Assert.Equal("tcp:2", forwards[0].Remote); + } + + [Fact] + public async void ListReverseForwardAsyncTest() + { + string[] responseMessages = new string[] + { + "(reverse) localabstract:scrcpy tcp:100\n(reverse) localabstract: scrcpy2 tcp:100\n(reverse) localabstract: scrcpy3 tcp:100\n" + }; + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }; + + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "reverse:list-forward" + }; + + ForwardData[] forwards = null; + + await RunTestAsync( + responses, + responseMessages, + requests, + async () => forwards = (await TestClient.ListReverseForwardAsync(Device)).ToArray()); + + Assert.NotNull(forwards); + Assert.Equal(3, forwards.Length); + Assert.Equal("(reverse)", forwards[0].SerialNumber); + Assert.Equal("localabstract:scrcpy", forwards[0].Local); + Assert.Equal("tcp:100", forwards[0].Remote); + } + + [Fact] + public async void ExecuteRemoteCommandAsyncTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK + }; + + string[] responseMessages = Array.Empty(); + + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "shell:echo Hello, World" + }; + + byte[] streamData = Encoding.ASCII.GetBytes("Hello, World\r\n"); + using MemoryStream shellStream = new(streamData); + + ConsoleOutputReceiver receiver = new(); + + await RunTestAsync( + responses, + responseMessages, + requests, + shellStream, + () => TestClient.ExecuteRemoteCommandAsync("echo Hello, World", device, receiver)); + + Assert.Equal("Hello, World\r\n", receiver.ToString(), ignoreLineEndingDifferences: true); + } + + [Fact] + public async void ExecuteRemoteCommandAsyncUnresponsiveTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK + }; + + string[] responseMessages = Array.Empty(); + + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "shell:echo Hello, World" + }; + + ConsoleOutputReceiver receiver = new(); + + _ = await Assert.ThrowsAsync(() => + RunTestAsync( + responses, + responseMessages, + requests, + null, + () => TestClient.ExecuteRemoteCommandAsync("echo Hello, World", device, receiver, CancellationToken.None))); + } + + [Fact] + public async void RunLogServiceAsyncTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK + }; + + string[] responseMessages = Array.Empty(); + + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "shell:logcat -B -b system" + }; + + ConsoleOutputReceiver receiver = new(); + + using Stream stream = File.OpenRead("Assets/logcat.bin"); + using ShellStream shellStream = new(stream, false); + Collection logs = new(); + Action sink = logs.Add; + + await RunTestAsync( + responses, + responseMessages, + requests, + shellStream, + () => TestClient.RunLogServiceAsync(device, sink, CancellationToken.None, LogId.System)); + + Assert.Equal(3, logs.Count); + } + + [Fact] + public async void RebootAsyncTest() + { + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "reboot:" + }; + + await RunTestAsync( + new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, + NoResponseMessages, + requests, + () => TestClient.RebootAsync(Device)); + } + + [Fact] + public async void PairAsyncIPAddressTest() => + await RunPairAsyncTest( + () => TestClient.PairAsync(IPAddress.Loopback, "114514"), + "127.0.0.1:5555", + "114514"); + + [Fact] + public async void PairAsyncDnsEndpointTest() => + await RunPairAsyncTest( + () => TestClient.PairAsync(new DnsEndPoint("localhost", 1234), "114514"), + "localhost:1234", + "114514"); + + [Fact] + public async void PairAsyncIPEndpointTest() => + await RunPairAsyncTest( + () => TestClient.PairAsync(new IPEndPoint(IPAddress.Loopback, 4321), "114514"), + "127.0.0.1:4321", + "114514"); + + [Fact] + public async void PairAsyncHostEndpointTest() => + await RunPairAsyncTest( + () => TestClient.PairAsync("localhost:9926", "114514"), + "localhost:9926", + "114514"); + + [Fact] + public async void PairAsyncIPAddressNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPAddress)null, "114514")); + + [Fact] + public async void PairAsyncDnsEndpointNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync(null, "114514")); + + [Fact] + public async void PairAsyncIPEndpointNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPEndPoint)null, "114514")); + + [Fact] + public async void PairAsyncHostEndpointNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((string)null, "114514")); + + [Fact] + public async void ConnectAsyncIPAddressTest() => + await RunConnectAsyncTest( + () => TestClient.ConnectAsync(IPAddress.Loopback), + "127.0.0.1:5555"); + + [Fact] + public async void ConnectAsyncDnsEndpointTest() => + await RunConnectAsyncTest( + () => TestClient.ConnectAsync(new DnsEndPoint("localhost", 1234)), + "localhost:1234"); + + [Fact] + public async void ConnectAsyncIPEndpointTest() => + await RunConnectAsyncTest( + () => TestClient.ConnectAsync(new IPEndPoint(IPAddress.Loopback, 4321)), + "127.0.0.1:4321"); + + [Fact] + public async void ConnectAsyncHostEndpointTest() => + await RunConnectAsyncTest( + () => TestClient.ConnectAsync("localhost:9926"), + "localhost:9926"); + + [Fact] + public async void ConnectAsyncIPAddressNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPAddress)null)); + + [Fact] + public async void ConnectAsyncDnsEndpointNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync(null)); + + [Fact] + public async void ConnectAsyncIPEndpointNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPEndPoint)null)); + + [Fact] + public async void ConnectAsyncHostEndpointNullTest() => + _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((string)null)); + + [Fact] + public async void DisconnectAsyncTest() + { + string[] requests = new string[] { "host:disconnect:localhost:5555" }; + string[] responseMessages = new string[] { "disconnected 127.0.0.1:5555" }; + + await RunTestAsync( + OkResponse, + responseMessages, + requests, + () => TestClient.DisconnectAsync(new DnsEndPoint("localhost", 5555))); + } + + [Fact] + public async void RootAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "root:" + }; + + byte[] expectedData = new byte[1024]; + byte[] expectedString = Encoding.UTF8.GetBytes("adbd cannot run as root in production builds\n"); + Buffer.BlockCopy(expectedString, 0, expectedData, 0, expectedString.Length); + + _ = await Assert.ThrowsAsync(() => + RunTestAsync( + new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, + NoResponseMessages, + requests, + Array.Empty<(SyncCommand, string)>(), + Array.Empty(), + new byte[][] { expectedData }, + Array.Empty(), + () => TestClient.RootAsync(device))); + } + + [Fact] + public async void UnrootAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "unroot:" + }; + + byte[] expectedData = new byte[1024]; + byte[] expectedString = Encoding.UTF8.GetBytes("adbd not running as root\n"); + Buffer.BlockCopy(expectedString, 0, expectedData, 0, expectedString.Length); + + _ = await Assert.ThrowsAsync(() => + RunTestAsync( + new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, + NoResponseMessages, + requests, + Array.Empty<(SyncCommand, string)>(), + Array.Empty(), + new byte[][] { expectedData }, + Array.Empty(), + () => TestClient.UnrootAsync(device))); + } + + [Fact] + public async void InstallAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "exec:cmd package 'install' -S 205774" + }; + + // The app data is sent in chunks of 32 kb + Collection applicationDataChuncks = new(); + + using (Stream stream = File.OpenRead("Assets/testapp.apk")) + { + while (true) + { + byte[] buffer = new byte[32 * 1024]; + int read = await stream.ReadAsync(buffer); + + if (read == 0) + { + break; + } + else + { + buffer = buffer.Take(read).ToArray(); + applicationDataChuncks.Add(buffer); + } + } + } + + byte[] response = Encoding.UTF8.GetBytes("Success\n"); + + using (Stream stream = File.OpenRead("Assets/testapp.apk")) + { + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + Array.Empty<(SyncCommand, string)>(), + Array.Empty(), + new byte[][] { response }, + applicationDataChuncks.ToArray(), + () => TestClient.InstallAsync(device, stream)); + } + } + + [Fact] + public async void InstallCreateAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "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 = string.Empty; + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + async () => session = await TestClient.InstallCreateAsync(device, "com.google.android.gms")); + + Assert.Equal("936013062", session); + } + + [Fact] + public async void InstallWriteAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "exec:cmd package 'install-write' -S 205774 936013062 base.apk" + }; + + // The app data is sent in chunks of 32 kb + Collection applicationDataChuncks = new(); + + using (Stream stream = File.OpenRead("Assets/testapp.apk")) + { + while (true) + { + byte[] buffer = new byte[32 * 1024]; + int read = await stream.ReadAsync(buffer); + + if (read == 0) + { + break; + } + else + { + buffer = buffer.Take(read).ToArray(); + applicationDataChuncks.Add(buffer); + } + } + } + + byte[] response = Encoding.UTF8.GetBytes("Success: streamed 205774 bytes\n"); + + using (Stream stream = File.OpenRead("Assets/testapp.apk")) + { + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + Array.Empty<(SyncCommand, string)>(), + Array.Empty(), + new byte[][] { response }, + applicationDataChuncks.ToArray(), + () => TestClient.InstallWriteAsync(device, stream, "base", "936013062")); + } + } + + [Fact] + public async void InstallCommitAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "exec:cmd package 'install-commit' 936013062" + }; + + byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); + using MemoryStream shellStream = new(streamData); + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => TestClient.InstallCommitAsync(device, "936013062")); + } + + [Fact] + public async void GetFeatureSetAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host-serial:009d1cd696d5194a:features" + }; + + string[] responses = new string[] + { + "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" + }; + + IEnumerable features = null; + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + }, + responses, + requests, + async () => features = await TestClient.GetFeatureSetAsync(device)); + + Assert.Equal(12, features.Count()); + Assert.Equal("sendrecv_v2_brotli", features.First()); + Assert.Equal("stat_v2", features.Last()); + } + + [Fact] + public async void DumpScreenStringAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "shell:uiautomator dump /dev/tty" + }; + + string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); + byte[] streamData = Encoding.UTF8.GetBytes(dump); + using MemoryStream shellStream = new(streamData); + + string xml = string.Empty; + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + async () => xml = await TestClient.DumpScreenStringAsync(device)); + + Assert.Equal(dump.Replace("Events injected: 1\r\n", "").Replace("UI hierchary dumped to: /dev/tty", "").Trim(), xml); + } + + [Fact] + public async void DumpScreenAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "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 = null; + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + async () => xml = await TestClient.DumpScreenAsync(device)); + + XmlDocument doc = new(); + doc.LoadXml(dump.Replace("Events injected: 1\r\n", "").Replace("UI hierchary dumped to: /dev/tty", "").Trim()); + + Assert.Equal(doc, xml); + } + + [Fact] + public async void FindElementAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "shell:uiautomator dump /dev/tty" + }; + + string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); + byte[] streamData = Encoding.UTF8.GetBytes(dump); + using MemoryStream shellStream = new(streamData); + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + async () => + { + Element element = await TestClient.FindElementAsync(device); + Assert.Equal(144, element.GetChildCount()); + element = element[0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", element.Attributes["text"]); + Assert.Equal(Area.FromLTRB(45, 889, 427, 973), element.Area); + }); + } + + [Fact] + public async void FindElementsAsyncTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "shell:uiautomator dump /dev/tty" + }; + + string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); + byte[] streamData = Encoding.UTF8.GetBytes(dump); + using MemoryStream shellStream = new(streamData); + + await RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + async () => + { + List elements = await TestClient.FindElementsAsync(device); + 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(Area.FromLTRB(45, 889, 427, 973), element.Area); + }); + } + + private Task RunConnectAsyncTest(Func test, string connectString) + { + string[] requests = new string[] + { + $"host:connect:{connectString}" + }; + + string[] responseMessages = new string[] + { + $"connected to {connectString}" + }; + + return RunTestAsync( + OkResponse, + responseMessages, + requests, + test); + } + + private Task RunPairAsyncTest(Func test, string connectString, string code) + { + string[] requests = new string[] + { + $"host:pair:{code}:{connectString}" + }; + + string[] responseMessages = new string[] + { + $"Successfully paired to {connectString} [guid=adb-996198a3-xPRwsQ]" + }; + + return RunTestAsync( + OkResponse, + responseMessages, + requests, + test); + } + + private Task RunCreateReverseAsyncTest(Func test, string reverseString) + { + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + $"reverse:forward:{reverseString}", + }; + + return RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + AdbResponse.OK + }, + new string[] + { + null + }, + requests, + () => test(Device)); + } + + private Task RunCreateForwardAsyncTest(Func test, string forwardString) + { + string[] requests = new string[] + { + $"host-serial:169.254.109.177:5555:forward:{forwardString}" + }; + + return RunTestAsync( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK + }, + new string[] + { + null + }, + requests, + () => test(Device)); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.cs index 7fe6b89c..b1faf3e9 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.cs @@ -7,8 +7,7 @@ using System.Linq; using System.Net; using System.Text; -using System.Threading; -using System.Threading.Tasks; +using System.Xml; using Xunit; namespace AdvancedSharpAdbClient.Tests @@ -16,24 +15,28 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class AdbClientTests : SocketBasedTests + public partial class AdbClientTests : SocketBasedTests { - // Toggle the integration test flag to true to run on an actual adb server - // (and to build/validate the test cases), set to false to use the mocked - // adb sockets. - // In release mode, this flag is ignored and the mocked adb sockets are always used. + /// + /// Toggle the integration test flag to true to run on an actual adb server + /// (and to build/validate the test cases), set to false to use the mocked + /// adb sockets. + /// In release mode, this flag is ignored and the mocked adb sockets are always used. + /// public AdbClientTests() : base(integrationTest: false, doDispose: false) { - lock (FactoriesTests.locker) - { - Factories.Reset(); - } } + /// + /// Tests the method. + /// [Fact] public void ConstructorNullTest() => _ = Assert.Throws(() => new AdbClient(null, Factories.AdbSocketFactory)); + /// + /// Tests the method. + /// [Fact] public void ConstructorInvalidEndPointTest() => _ = Assert.Throws(() => new AdbClient(new CustomEndPoint(), Factories.AdbSocketFactory)); @@ -52,6 +55,9 @@ public void ConstructorTest() Assert.Equal(AdbClient.AdbServerPort, endPoint.Port); } + /// + /// Tests the method. + /// [Fact] public void FormAdbRequestTest() { @@ -59,6 +65,9 @@ public void FormAdbRequestTest() Assert.Equal(Encoding.ASCII.GetBytes("000Chost:version"), AdbClient.FormAdbRequest("host:version")); } + /// + /// Tests the method. + /// [Fact] public void CreateAdbForwardRequestTest() { @@ -66,21 +75,9 @@ public void CreateAdbForwardRequestTest() Assert.Equal(Encoding.ASCII.GetBytes("0012tcp:1981:127.0.0.1"), AdbClient.CreateAdbForwardRequest("127.0.0.1", 1981)); } - [Fact] - public void KillAdbTest() - { - string[] requests = new string[] - { - "host:kill" - }; - - RunTest( - NoResponses, - NoResponseMessages, - requests, - TestClient.KillAdb); - } - + /// + /// Tests the method. + /// [Fact] public void GetAdbVersionTest() { @@ -106,6 +103,27 @@ public void GetAdbVersionTest() Assert.Equal(32, version); } + /// + /// Tests the method. + /// + [Fact] + public void KillAdbTest() + { + string[] requests = new string[] + { + "host:kill" + }; + + RunTest( + NoResponses, + NoResponseMessages, + requests, + TestClient.KillAdb); + } + + /// + /// Tests the method. + /// [Fact] public void GetDevicesTest() { @@ -139,6 +157,9 @@ public void GetDevicesTest() Assert.Equal("donatello", device.Name); } + /// + /// Tests the method. + /// [Fact] public void SetDeviceTest() { @@ -154,6 +175,9 @@ public void SetDeviceTest() () => Socket.SetDevice(Device)); } + /// + /// Tests the method. + /// [Fact] public void SetInvalidDeviceTest() { @@ -170,6 +194,9 @@ public void SetInvalidDeviceTest() () => Socket.SetDevice(Device))); } + /// + /// Tests the method. + /// [Fact] public void SetDeviceOtherException() { @@ -186,158 +213,155 @@ public void SetDeviceOtherException() () => Socket.SetDevice(Device))); } + /// + /// Tests the method. + /// [Fact] - public void RebootTest() + public void CreateForwardTest() => + RunCreateForwardTest( + (device) => TestClient.CreateForward(device, "tcp:1", "tcp:2", true), + "tcp:1;tcp:2"); + + /// + /// Tests the method. + /// + [Fact] + public void CreateReverseTest() => + RunCreateReverseTest( + (device) => TestClient.CreateReverseForward(device, "tcp:1", "tcp:2", true), + "tcp:1;tcp:2"); + + /// + /// Tests the method. + /// + [Fact] + public void CreateTcpForwardTest() => + RunCreateForwardTest( + (device) => TestClient.CreateForward(device, 3, 4), + "tcp:3;tcp:4"); + + /// + /// Tests the method. + /// + [Fact] + public void CreateSocketForwardTest() => + RunCreateForwardTest( + (device) => TestClient.CreateForward(device, 5, "/socket/1"), + "tcp:5;local:/socket/1"); + + /// + /// Tests the method. + /// + [Fact] + public void CreateDuplicateForwardTest() { + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.FromError("cannot rebind existing socket") + }; + string[] requests = new string[] { - "host:transport:169.254.109.177:5555", - "reboot:" + "host-serial:169.254.109.177:5555:forward:norebind:tcp:1;tcp:2" }; + _ = Assert.Throws(() => RunTest( - new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, + responses, NoResponseMessages, requests, - () => - TestClient.Reboot(Device)); + () => TestClient.CreateForward(Device, "tcp:1", "tcp:2", false))); } + /// + /// Tests the method. + /// [Fact] - public void ExecuteRemoteCommandTest() + public void RemoveForwardTest() { - DeviceData device = new() - { - Serial = "169.254.109.177:5555", - State = DeviceState.Online - }; - - AdbResponse[] responses = new AdbResponse[] - { - AdbResponse.OK, - AdbResponse.OK - }; - - string[] responseMessages = Array.Empty(); - string[] requests = new string[] { - "host:transport:169.254.109.177:5555", - "shell:echo Hello, World" + "host-serial:169.254.109.177:5555:killforward:tcp:1" }; - byte[] streamData = Encoding.ASCII.GetBytes("Hello, World\r\n"); - MemoryStream shellStream = new(streamData); - - ConsoleOutputReceiver receiver = new(); - RunTest( - responses, - responseMessages, + OkResponse, + NoResponseMessages, requests, - shellStream, - () => TestClient.ExecuteRemoteCommand("echo Hello, World", device, receiver)); - - Assert.Equal("Hello, World\r\n", receiver.ToString(), ignoreLineEndingDifferences: true); + () => TestClient.RemoveForward(Device, 1)); } + /// + /// Tests the method. + /// [Fact] - public void ExecuteRemoteCommandUnresponsiveTest() + public void RemoveReverseForwardTest() { - DeviceData device = new() + string[] requests = new string[] { - Serial = "169.254.109.177:5555", - State = DeviceState.Online + "host:transport:169.254.109.177:5555", + "reverse:killforward:localabstract:test" }; AdbResponse[] responses = new AdbResponse[] { AdbResponse.OK, - AdbResponse.OK + AdbResponse.OK, }; - string[] responseMessages = Array.Empty(); + RunTest( + responses, + NoResponseMessages, + requests, + () => TestClient.RemoveReverseForward(Device, "localabstract:test")); + } + /// + /// Tests the method. + /// + [Fact] + public void RemoveAllForwardsTest() + { string[] requests = new string[] { - "host:transport:169.254.109.177:5555", - "shell:echo Hello, World" + "host-serial:169.254.109.177:5555:killforward-all" }; - ConsoleOutputReceiver receiver = new(); - - _ = Assert.Throws(() => RunTest( - responses, - responseMessages, + OkResponse, + NoResponseMessages, requests, - null, - () => - { - try - { - TestClient.ExecuteRemoteCommandAsync("echo Hello, World", device, receiver, CancellationToken.None).Wait(); - } - catch (AggregateException ex) - { - if (ex.InnerExceptions.Count == 1) - { - throw ex.InnerException; - } - else - { - throw; - } - } - })); + () => TestClient.RemoveAllForwards(Device)); } + /// + /// Tests the method. + /// [Fact] - public void CreateForwardTest() => - RunCreateForwardTest( - (device) => TestClient.CreateForward(device, "tcp:1", "tcp:2", true), - "tcp:1;tcp:2"); - - - [Fact] - public void CreateReverseTest() => - RunCreateReverseTest( - (device) => TestClient.CreateReverseForward(device, "tcp:1", "tcp:2", true), - "tcp:1;tcp:2"); - - [Fact] - public void CreateTcpForwardTest() => - RunCreateForwardTest( - (device) => TestClient.CreateForward(device, 3, 4), - "tcp:3;tcp:4"); - - [Fact] - public void CreateSocketForwardTest() => - RunCreateForwardTest( - (device) => TestClient.CreateForward(device, 5, "/socket/1"), - "tcp:5;local:/socket/1"); - - [Fact] - public void CreateDuplicateForwardTest() + public void RemoveAllReversesTest() { - AdbResponse[] responses = new AdbResponse[] + string[] requests = new string[] { - AdbResponse.FromError("cannot rebind existing socket") + "host:transport:169.254.109.177:5555", + "reverse:killforward-all" }; - string[] requests = new string[] + AdbResponse[] responses = new AdbResponse[] { - "host-serial:169.254.109.177:5555:forward:norebind:tcp:1;tcp:2" + AdbResponse.OK, + AdbResponse.OK, }; - _ = Assert.Throws(() => RunTest( responses, NoResponseMessages, requests, - () => TestClient.CreateForward(Device, "tcp:1", "tcp:2", false))); + () => TestClient.RemoveAllReverseForwards(Device)); } + /// + /// Tests the method. + /// [Fact] public void ListForwardTest() { @@ -366,6 +390,9 @@ public void ListForwardTest() Assert.Equal("tcp:2", forwards[0].Remote); } + /// + /// Tests the method. + /// [Fact] public void ListReverseForwardTest() { @@ -400,80 +427,146 @@ public void ListReverseForwardTest() Assert.Equal("tcp:100", forwards[0].Remote); } + /// + /// Tests the method. + /// [Fact] - public void RemoveForwardTest() + public void ExecuteRemoteCommandTest() { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + AdbResponse[] responses = new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK + }; + + string[] responseMessages = Array.Empty(); + string[] requests = new string[] { - "host-serial:169.254.109.177:5555:killforward:tcp:1" + "host:transport:169.254.109.177:5555", + "shell:echo Hello, World" }; + byte[] streamData = Encoding.ASCII.GetBytes("Hello, World\r\n"); + using MemoryStream shellStream = new(streamData); + + ConsoleOutputReceiver receiver = new(); + RunTest( - OkResponse, - NoResponseMessages, + responses, + responseMessages, requests, - () => TestClient.RemoveForward(Device, 1)); + shellStream, + () => TestClient.ExecuteRemoteCommand("echo Hello, World", device, receiver)); + + Assert.Equal("Hello, World\r\n", receiver.ToString(), ignoreLineEndingDifferences: true); } + /// + /// Tests the method. + /// [Fact] - public void RemoveReverseForwardTest() + public void ExecuteRemoteCommandUnresponsiveTest() { - string[] requests = new string[] + DeviceData device = new() { - "host:transport:169.254.109.177:5555", - "reverse:killforward:localabstract:test" + Serial = "169.254.109.177:5555", + State = DeviceState.Online }; AdbResponse[] responses = new AdbResponse[] { AdbResponse.OK, - AdbResponse.OK, + AdbResponse.OK }; - RunTest( - responses, - NoResponseMessages, - requests, - () => TestClient.RemoveReverseForward(Device, "localabstract:test")); - } + string[] responseMessages = Array.Empty(); - [Fact] - public void RemoveAllForwardsTest() - { string[] requests = new string[] { - "host-serial:169.254.109.177:5555:killforward-all" + "host:transport:169.254.109.177:5555", + "shell:echo Hello, World" }; + ConsoleOutputReceiver receiver = new(); + + _ = Assert.Throws(() => RunTest( - OkResponse, - NoResponseMessages, + responses, + responseMessages, requests, - () => TestClient.RemoveAllForwards(Device)); + null, + () => TestClient.ExecuteRemoteCommand("echo Hello, World", device, receiver))); } [Fact] - public void RemoveAllReversesTest() + public void RunLogServiceTest() { - string[] requests = new string[] + DeviceData device = new() { - "host:transport:169.254.109.177:5555", - "reverse:killforward-all" + Serial = "169.254.109.177:5555", + State = DeviceState.Online }; AdbResponse[] responses = new AdbResponse[] { AdbResponse.OK, - AdbResponse.OK, + AdbResponse.OK }; - RunTest( - responses, + string[] responseMessages = Array.Empty(); + + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "shell:logcat -B -b system" + }; + + ConsoleOutputReceiver receiver = new(); + + using Stream stream = File.OpenRead("Assets/logcat.bin"); + using ShellStream shellStream = new(stream, false); + Collection logs = new(); + Action sink = logs.Add; + + RunTest( + responses, + responseMessages, + requests, + shellStream, + () => TestClient.RunLogService(device, sink, LogId.System)); + + Assert.Equal(3, logs.Count); + } + + /// + /// Tests the method. + /// + [Fact] + public void RebootTest() + { + string[] requests = new string[] + { + "host:transport:169.254.109.177:5555", + "reboot:" + }; + + RunTest( + new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, NoResponseMessages, requests, - () => TestClient.RemoveAllReverseForwards(Device)); + () => TestClient.Reboot(Device)); } + /// + /// Tests the method. + /// [Fact] public void PairIPAddressTest() => RunPairTest( @@ -481,6 +574,9 @@ public void PairIPAddressTest() => "127.0.0.1:5555", "114514"); + /// + /// Tests the method. + /// [Fact] public void PairDnsEndpointTest() => RunPairTest( @@ -488,6 +584,9 @@ public void PairDnsEndpointTest() => "localhost:1234", "114514"); + /// + /// Tests the method. + /// [Fact] public void PairIPEndpointTest() => RunPairTest( @@ -495,6 +594,9 @@ public void PairIPEndpointTest() => "127.0.0.1:4321", "114514"); + /// + /// Tests the method. + /// [Fact] public void PairHostEndpointTest() => RunPairTest( @@ -502,62 +604,101 @@ public void PairHostEndpointTest() => "localhost:9926", "114514"); + /// + /// Tests the method. + /// [Fact] - public async Task PairIPAddressNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPAddress)null, "114514")); + public void PairIPAddressNullTest() => + _ = Assert.Throws(() => TestClient.Pair((IPAddress)null, "114514")); + /// + /// Tests the method. + /// [Fact] - public async Task PairDnsEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync(null, "114514")); + public void PairDnsEndpointNullTest() => + _ = Assert.Throws(() => TestClient.Pair(null, "114514")); + /// + /// Tests the method. + /// [Fact] - public async Task PairIPEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((IPEndPoint)null, "114514")); + public void PairIPEndpointNullTest() => + _ = Assert.Throws(() => TestClient.Pair((IPEndPoint)null, "114514")); + /// + /// Tests the method. + /// [Fact] - public async Task PairHostEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.PairAsync((string)null, "114514")); + public void PairHostEndpointNullTest() => + _ = Assert.Throws(() => TestClient.Pair((string)null, "114514")); + /// + /// Tests the method. + /// [Fact] public void ConnectIPAddressTest() => RunConnectTest( () => TestClient.Connect(IPAddress.Loopback), "127.0.0.1:5555"); + /// + /// Tests the method. + /// [Fact] public void ConnectDnsEndpointTest() => RunConnectTest( () => TestClient.Connect(new DnsEndPoint("localhost", 1234)), "localhost:1234"); + /// + /// Tests the method. + /// [Fact] public void ConnectIPEndpointTest() => RunConnectTest( () => TestClient.Connect(new IPEndPoint(IPAddress.Loopback, 4321)), "127.0.0.1:4321"); + /// + /// Tests the method. + /// [Fact] public void ConnectHostEndpointTest() => RunConnectTest( () => TestClient.Connect("localhost:9926"), "localhost:9926"); + /// + /// Tests the method. + /// [Fact] - public async Task ConnectIPAddressNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPAddress)null)); + public void ConnectIPAddressNullTest() => + _ = Assert.Throws(() => TestClient.Connect((IPAddress)null)); + /// + /// Tests the method. + /// [Fact] - public async Task ConnectDnsEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync(null)); + public void ConnectDnsEndpointNullTest() => + _ = Assert.Throws(() => TestClient.Connect(null)); + /// + /// Tests the method. + /// [Fact] - public async Task ConnectIPEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((IPEndPoint)null)); + public void ConnectIPEndpointNullTest() => + _ = Assert.Throws(() => TestClient.Connect((IPEndPoint)null)); + /// + /// Tests the method. + /// [Fact] - public async Task ConnectHostEndpointNullTest() => - _ = await Assert.ThrowsAsync(() => TestClient.ConnectAsync((string)null)); + public void ConnectHostEndpointNullTest() => + _ = Assert.Throws(() => TestClient.Connect((string)null)); + /// + /// Tests the method. + /// [Fact] public void DisconnectTest() { @@ -571,46 +712,9 @@ public void DisconnectTest() () => TestClient.Disconnect(new DnsEndPoint("localhost", 5555))); } - [Fact] - public void ReadLogTest() - { - DeviceData device = new() - { - Serial = "169.254.109.177:5555", - State = DeviceState.Online - }; - - AdbResponse[] responses = new AdbResponse[] - { - AdbResponse.OK, - AdbResponse.OK - }; - - string[] responseMessages = Array.Empty(); - - string[] requests = new string[] - { - "host:transport:169.254.109.177:5555", - "shell:logcat -B -b system" - }; - - ConsoleOutputReceiver receiver = new(); - - using Stream stream = File.OpenRead("Assets/logcat.bin"); - using ShellStream shellStream = new(stream, false); - Collection logs = new(); - Action sink = logs.Add; - - RunTest( - responses, - responseMessages, - requests, - shellStream, - () => TestClient.RunLogServiceAsync(device, sink, CancellationToken.None, LogId.System).Wait()); - - Assert.Equal(3, logs.Count); - } - + /// + /// Tests the method. + /// [Fact] public void RootTest() { @@ -642,6 +746,9 @@ public void RootTest() () => TestClient.Root(device))); } + /// + /// Tests the method. + /// [Fact] public void UnrootTest() { @@ -673,6 +780,9 @@ public void UnrootTest() () => TestClient.Unroot(device))); } + /// + /// Tests the method. + /// [Fact] public void InstallTest() { @@ -685,7 +795,7 @@ public void InstallTest() string[] requests = new string[] { "host:transport:009d1cd696d5194a", - "exec:cmd package 'install' -S 205774" + "exec:cmd package 'install' -S 205774" }; // The app data is sent in chunks of 32 kb @@ -730,16 +840,347 @@ public void InstallTest() } } - private void RunPairTest(Action test, string connectString, string code) + /// + /// Tests the method. + /// + [Fact] + public void InstallCreateTest() { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + string[] requests = new string[] { - $"host:pair:{code}:{connectString}" + "host:transport:009d1cd696d5194a", + "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 = string.Empty; + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => session = TestClient.InstallCreate(device, "com.google.android.gms")); + + Assert.Equal("936013062", session); + } + + /// + /// Tests the method. + /// + [Fact] + public void InstallWriteTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "exec:cmd package 'install-write' -S 205774 936013062 base.apk" + }; + + // The app data is sent in chunks of 32 kb + Collection applicationDataChuncks = new(); + + using (Stream stream = File.OpenRead("Assets/testapp.apk")) + { + while (true) + { + byte[] buffer = new byte[32 * 1024]; + int read = stream.Read(buffer, 0, buffer.Length); + + if (read == 0) + { + break; + } + else + { + buffer = buffer.Take(read).ToArray(); + applicationDataChuncks.Add(buffer); + } + } + } + + byte[] response = Encoding.UTF8.GetBytes("Success: streamed 205774 bytes\n"); + + using (Stream stream = File.OpenRead("Assets/testapp.apk")) + { + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + Array.Empty<(SyncCommand, string)>(), + Array.Empty(), + new byte[][] { response }, + applicationDataChuncks.ToArray(), + () => TestClient.InstallWrite(device, stream, "base", "936013062")); + } + } + + /// + /// Tests the method. + /// + [Fact] + public void InstallCommitTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "exec:cmd package 'install-commit' 936013062" + }; + + byte[] streamData = Encoding.ASCII.GetBytes("Success\r\n"); + using MemoryStream shellStream = new(streamData); + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => TestClient.InstallCommit(device, "936013062")); + } + + /// + /// Tests the method. + /// + [Fact] + public void GetFeatureSetTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host-serial:009d1cd696d5194a:features" + }; + + string[] responses = new string[] + { + "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" + }; + + IEnumerable features = null; + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + }, + responses, + requests, + () => features = TestClient.GetFeatureSet(device)); + + Assert.Equal(12, features.Count()); + Assert.Equal("sendrecv_v2_brotli", features.First()); + Assert.Equal("stat_v2", features.Last()); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenStringTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "shell:uiautomator dump /dev/tty" + }; + + string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); + byte[] streamData = Encoding.UTF8.GetBytes(dump); + using MemoryStream shellStream = new(streamData); + + string xml = string.Empty; + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => xml = TestClient.DumpScreenString(device)); + + Assert.Equal(dump.Replace("Events injected: 1\r\n", "").Replace("UI hierchary dumped to: /dev/tty", "").Trim(), xml); + } + + /// + /// Tests the method. + /// + [Fact] + public void DumpScreenTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "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 = null; + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => xml = TestClient.DumpScreen(device)); + + XmlDocument doc = new(); + doc.LoadXml(dump.Replace("Events injected: 1\r\n", "").Replace("UI hierchary dumped to: /dev/tty", "").Trim()); + + Assert.Equal(doc, xml); + } + + /// + /// Tests the method. + /// + [Fact] + public void FindElementTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "shell:uiautomator dump /dev/tty" + }; + + string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); + byte[] streamData = Encoding.UTF8.GetBytes(dump); + using MemoryStream shellStream = new(streamData); + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => + { + Element element = TestClient.FindElement(device); + Assert.Equal(144, element.GetChildCount()); + element = element[0][0][0][0][0][0][0][0][2][1][0][0]; + Assert.Equal("where-where", element.Attributes["text"]); + Assert.Equal(Area.FromLTRB(45, 889, 427, 973), element.Area); + }); + } + + /// + /// Tests the method. + /// + [Fact] + public void FindElementsTest() + { + DeviceData device = new() + { + Serial = "009d1cd696d5194a", + State = DeviceState.Online + }; + + string[] requests = new string[] + { + "host:transport:009d1cd696d5194a", + "shell:uiautomator dump /dev/tty" + }; + + string dump = File.ReadAllText(@"Assets/dumpscreen.txt"); + byte[] streamData = Encoding.UTF8.GetBytes(dump); + using MemoryStream shellStream = new(streamData); + + RunTest( + new AdbResponse[] + { + AdbResponse.OK, + AdbResponse.OK, + }, + NoResponseMessages, + requests, + shellStream, + () => + { + List elements = TestClient.FindElements(device).ToList(); + 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(Area.FromLTRB(45, 889, 427, 973), element.Area); + }); + } + + private void RunConnectTest(Action test, string connectString) + { + string[] requests = new string[] + { + $"host:connect:{connectString}" }; string[] responseMessages = new string[] { - $"Successfully paired to {connectString} [guid=adb-996198a3-xPRwsQ]" + $"connected to {connectString}" }; RunTest( @@ -749,16 +1190,16 @@ private void RunPairTest(Action test, string connectString, string code) test); } - private void RunConnectTest(Action test, string connectString) + private void RunPairTest(Action test, string connectString, string code) { string[] requests = new string[] { - $"host:connect:{connectString}" + $"host:pair:{code}:{connectString}" }; string[] responseMessages = new string[] { - $"connected to {connectString}" + $"Successfully paired to {connectString} [guid=adb-996198a3-xPRwsQ]" }; RunTest( diff --git a/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs new file mode 100644 index 00000000..6430112b --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.Async.cs @@ -0,0 +1,50 @@ +using AdvancedSharpAdbClient.Exceptions; +using System; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class AdbCommandLineClientTests + { + [Fact] + public async void GetVersionAsyncTest() + { + DummyAdbCommandLineClient commandLine = new() + { + Version = new Version(1, 0, 32) + }; + + Assert.Equal(new Version(1, 0, 32), await commandLine.GetVersionAsync()); + } + + [Fact] + public async void GetVersionAsyncNullTest() + { + DummyAdbCommandLineClient commandLine = new() + { + Version = null + }; + _ = await Assert.ThrowsAsync(() => commandLine.GetVersionAsync()); + } + + [Fact] + public async void GetOutdatedVersionAsyncTest() + { + DummyAdbCommandLineClient commandLine = new() + { + Version = new Version(1, 0, 1) + }; + + _ = await Assert.ThrowsAsync(() => commandLine.GetVersionAsync()); + } + + [Fact] + public async void StartServerAsyncTest() + { + DummyAdbCommandLineClient commandLine = new(); + Assert.False(commandLine.ServerStarted); + await commandLine.StartServerAsync(); + Assert.True(commandLine.ServerStarted); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.cs b/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.cs index 1b93282f..74d33fcb 100644 --- a/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.cs +++ b/AdvancedSharpAdbClient.Tests/AdbCommandLineClientTests.cs @@ -7,7 +7,7 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class AdbCommandLineClientTests + public partial class AdbCommandLineClientTests { [Fact] public void GetVersionTest() diff --git a/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs new file mode 100644 index 00000000..57d93adb --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/AdbServerTests.Async.cs @@ -0,0 +1,187 @@ +using AdvancedSharpAdbClient.Exceptions; +using Moq; +using System; +using System.Net.Sockets; +using System.Threading; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class AdbServerTests + { + [Fact] + public async void GetStatusAsyncNotRunningTest() + { + Mock adbClientMock = new(); + adbClientMock.Setup(c => c.GetAdbVersionAsync(It.IsAny())) + .Throws(new AggregateException(new SocketException(AdbServer.ConnectionRefused))); + + AdbServer adbServer = new(adbClientMock.Object, adbCommandLineClientFactory); + + AdbServerStatus status = await adbServer.GetStatusAsync(); + Assert.False(status.IsRunning); + Assert.Null(status.Version); + } + + [Fact] + public async void GetStatusAsyncRunningTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("0020"); + + AdbServerStatus status = await adbServer.GetStatusAsync(); + + Assert.Empty(socket.Responses); + Assert.Empty(socket.ResponseMessages); + Assert.Single(socket.Requests); + Assert.Equal("host:version", socket.Requests[0]); + + Assert.True(status.IsRunning); + Assert.Equal(new Version(1, 0, 32), status.Version); + } + + [Fact] + public async void GetStatusAsyncOtherSocketExceptionTest() + { + adbSocketFactory = (endPoint) => throw new SocketException(); + + adbClient = new AdbClient(AdbClient.DefaultEndPoint, adbSocketFactory); + adbServer = new AdbServer(adbClient, adbCommandLineClientFactory); + + _ = await Assert.ThrowsAsync(async () => await adbServer.GetStatusAsync()); + } + + [Fact] + public async void GetStatusAsyncOtherExceptionTest() + { + adbSocketFactory = (endPoint) => throw new Exception(); + + adbClient = new AdbClient(AdbClient.DefaultEndPoint, adbSocketFactory); + adbServer = new AdbServer(adbClient, adbCommandLineClientFactory); + + _ = await Assert.ThrowsAsync(async () => await adbServer.GetStatusAsync()); + } + + [Fact] + public async void StartServerAsyncAlreadyRunningTest() + { + commandLineClient.Version = new Version(1, 0, 20); + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("0020"); + + StartServerResult result = await adbServer.StartServerAsync(null, false); + + Assert.Equal(StartServerResult.AlreadyRunning, result); + + Assert.Single(socket.Requests); + Assert.Equal("host:version", socket.Requests[0]); + } + + [Fact] + public async void StartServerAsyncOutdatedRunningNoExecutableTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("0010"); + + _ = await Assert.ThrowsAsync(async () => await adbServer.StartServerAsync(null, false)); + } + + [Fact] + public async void StartServerAsyncNotRunningNoExecutableTest() + { + adbSocketFactory = (endPoint) => throw new SocketException(AdbServer.ConnectionRefused); + + adbClient = new AdbClient(AdbClient.DefaultEndPoint, adbSocketFactory); + adbServer = new AdbServer(adbClient, adbCommandLineClientFactory); + + _ = await Assert.ThrowsAsync(async () => await adbServer.StartServerAsync(null, false)); + } + + [Fact] + public async void StartServerAsyncOutdatedRunningTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("0010"); + + commandLineClient.Version = new Version(1, 0, 32); + + Assert.False(commandLineClient.ServerStarted); + _ = await adbServer.StartServerAsync(ServerName, false); + + Assert.True(commandLineClient.ServerStarted); + + Assert.Equal(2, socket.Requests.Count); + Assert.Equal("host:version", socket.Requests[0]); + Assert.Equal("host:kill", socket.Requests[1]); + } + + [Fact] + public async void StartServerAsyncNotRunningTest() + { + adbSocketFactory = (endPoint) => throw new SocketException(AdbServer.ConnectionRefused); + + adbClient = new AdbClient(AdbClient.DefaultEndPoint, adbSocketFactory); + adbServer = new AdbServer(adbClient, adbCommandLineClientFactory); + + commandLineClient.Version = new Version(1, 0, 32); + + Assert.False(commandLineClient.ServerStarted); + + StartServerResult result = await adbServer.StartServerAsync(ServerName, false); + + Assert.True(commandLineClient.ServerStarted); + } + + [Fact] + public async void StartServerAsyncIntermediateRestartRequestedRunningTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("001f"); + + commandLineClient.Version = new Version(1, 0, 32); + + Assert.False(commandLineClient.ServerStarted); + _ = await adbServer.StartServerAsync(ServerName, true); + + Assert.True(commandLineClient.ServerStarted); + + Assert.Equal(2, socket.Requests.Count); + Assert.Equal("host:version", socket.Requests[0]); + Assert.Equal("host:kill", socket.Requests[1]); + } + + [Fact] + public async void StartServerAsyncIntermediateRestartNotRequestedRunningTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("001f"); + + commandLineClient.Version = new Version(1, 0, 32); + + Assert.False(commandLineClient.ServerStarted); + _ = await adbServer.StartServerAsync(ServerName, false); + + Assert.False(commandLineClient.ServerStarted); + + Assert.Single(socket.Requests); + Assert.Equal("host:version", socket.Requests[0]); + } + + [Fact] + public async void RestartServerAsyncTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("001f"); + + commandLineClient.Version = new Version(1, 0, 32); + + Assert.False(commandLineClient.ServerStarted); + _ = await adbServer.RestartServerAsync(ServerName); + + Assert.False(commandLineClient.ServerStarted); + + Assert.Single(socket.Requests); + Assert.Equal("host:version", socket.Requests[0]); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/AdbServerTests.cs b/AdvancedSharpAdbClient.Tests/AdbServerTests.cs index 97f5a297..ed053249 100644 --- a/AdvancedSharpAdbClient.Tests/AdbServerTests.cs +++ b/AdvancedSharpAdbClient.Tests/AdbServerTests.cs @@ -11,7 +11,7 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class AdbServerTests + public partial class AdbServerTests { private readonly Func adbCommandLineClientFactory; private readonly DummyAdbSocket socket; @@ -26,6 +26,7 @@ public AdbServerTests() adbSocketFactory = (endPoint) => socket; commandLineClient = new DummyAdbCommandLineClient(); + AdbServer.IsValidAdbFile = commandLineClient.IsValidAdbFile; adbCommandLineClientFactory = (version) => commandLineClient; adbClient = new AdbClient(AdbClient.DefaultEndPoint, adbSocketFactory); @@ -190,6 +191,23 @@ public void StartServerIntermediateRestartNotRequestedRunningTest() Assert.Equal("host:version", socket.Requests[0]); } + [Fact] + public void RestartServerTest() + { + socket.Responses.Enqueue(AdbResponse.OK); + socket.ResponseMessages.Enqueue("001f"); + + commandLineClient.Version = new Version(1, 0, 32); + + Assert.False(commandLineClient.ServerStarted); + _ = adbServer.RestartServer(ServerName); + + Assert.False(commandLineClient.ServerStarted); + + Assert.Single(socket.Requests); + Assert.Equal("host:version", socket.Requests[0]); + } + [Fact] public void ConstructorAdbClientNullTest() => _ = Assert.Throws(() => new AdbServer(null, adbCommandLineClientFactory)); diff --git a/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs new file mode 100644 index 00000000..bf3b7294 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/AdbSocketTests.Async.cs @@ -0,0 +1,154 @@ +using AdvancedSharpAdbClient.Exceptions; +using System; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class AdbSocketTests + { + [Fact] + public async void SendSyncDATARequestAsyncTest() => + await RunTestAsync( + (socket) => socket.SendSyncRequestAsync(SyncCommand.DATA, 2, CancellationToken.None), + new byte[] { (byte)'D', (byte)'A', (byte)'T', (byte)'A', 2, 0, 0, 0 }); + + [Fact] + public async void SendSyncSENDRequestAsyncTest() => + await RunTestAsync( + (socket) => socket.SendSyncRequestAsync(SyncCommand.SEND, "/test", CancellationToken.None), + new byte[] { (byte)'S', (byte)'E', (byte)'N', (byte)'D', 5, 0, 0, 0, (byte)'/', (byte)'t', (byte)'e', (byte)'s', (byte)'t' }); + + [Fact] + public async void SendSyncDENTRequestAsyncTest() => + await RunTestAsync( + (socket) => socket.SendSyncRequestAsync(SyncCommand.DENT, "/data", 633, CancellationToken.None), + new byte[] { (byte)'D', (byte)'E', (byte)'N', (byte)'T', 9, 0, 0, 0, (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)',', (byte)'6', (byte)'3', (byte)'3' }); + + [Fact] + public async void SendSyncNullRequestAsyncTest() => + _ = await Assert.ThrowsAsync(() => RunTestAsync((socket) => socket.SendSyncRequestAsync(SyncCommand.DATA, null, CancellationToken.None), Array.Empty())); + + [Fact] + public async void ReadSyncResponseAsync() + { + DummyTcpSocket tcpSocket = new(); + AdbSocket socket = new(tcpSocket); + + using (StreamWriter writer = new(tcpSocket.InputStream, Encoding.ASCII, 4, true)) + { + await writer.WriteAsync("DENT"); + } + + tcpSocket.InputStream.Position = 0; + + Assert.Equal(SyncCommand.DENT, await socket.ReadSyncResponseAsync()); + } + + [Fact] + public async void ReadStringAsyncTest() + { + DummyTcpSocket tcpSocket = new(); + AdbSocket socket = new(tcpSocket); + + using (BinaryWriter writer = new(tcpSocket.InputStream, Encoding.ASCII, true)) + { + writer.Write(Encoding.ASCII.GetBytes(5.ToString("X4"))); + writer.Write(Encoding.ASCII.GetBytes("Hello")); + writer.Flush(); + } + + tcpSocket.InputStream.Position = 0; + + Assert.Equal("Hello", await socket.ReadStringAsync()); + } + + [Fact] + public async void ReadAdbOkayResponseAsyncTest() + { + DummyTcpSocket tcpSocket = new(); + AdbSocket socket = new(tcpSocket); + + using (StreamWriter writer = new(tcpSocket.InputStream, Encoding.ASCII, 4, true)) + { + await writer.WriteAsync("OKAY"); + } + + tcpSocket.InputStream.Position = 0; + + AdbResponse response = await socket.ReadAdbResponseAsync(); + Assert.True(response.IOSuccess); + Assert.Equal(string.Empty, response.Message); + Assert.True(response.Okay); + Assert.False(response.Timeout); + } + + [Fact] + public async void ReadAdbFailResponseAsyncTest() + { + DummyTcpSocket tcpSocket = new(); + AdbSocket socket = new(tcpSocket); + + using (StreamWriter writer = new(tcpSocket.InputStream, Encoding.ASCII, 4, true)) + { + await writer.WriteAsync("FAIL"); + await writer.WriteAsync(17.ToString("X4")); + await writer.WriteAsync("This did not work"); + } + + tcpSocket.InputStream.Position = 0; + + _ = await Assert.ThrowsAsync(() => socket.ReadAdbResponseAsync()); + } + + [Fact] + public async void ReadAsyncTest() + { + DummyTcpSocket tcpSocket = new(); + AdbSocket socket = new(tcpSocket); + + // Read 100 bytes from a stream which has 101 bytes available + byte[] data = new byte[101]; + for (int i = 0; i < 101; i++) + { + data[i] = (byte)i; + } + + await tcpSocket.InputStream.WriteAsync(data, 0, 101); + tcpSocket.InputStream.Position = 0; + + // Buffer has a capacity of 101, but we'll only want to read 100 bytes + byte[] received = new byte[101]; + + await socket.ReadAsync(received, 100, CancellationToken.None); + + for (int i = 0; i < 100; i++) + { + Assert.Equal(received[i], (byte)i); + } + + Assert.Equal(0, received[100]); + } + + [Fact] + public async void SendAdbRequestAsyncTest() => + await RunTestAsync( + (socket) => socket.SendAdbRequestAsync("Test", CancellationToken.None), + Encoding.ASCII.GetBytes("0004Test")); + + private static async Task RunTestAsync(Func test, byte[] expectedDataSent) + { + DummyTcpSocket tcpSocket = new(); + AdbSocket socket = new(tcpSocket); + + // Run the test. + await test(socket); + + // Validate the data that was sent over the wire. + Assert.Equal(expectedDataSent, tcpSocket.GetBytesSent()); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/AdbSocketTests.cs b/AdvancedSharpAdbClient.Tests/AdbSocketTests.cs index a1c3a203..a6b11ba9 100644 --- a/AdvancedSharpAdbClient.Tests/AdbSocketTests.cs +++ b/AdvancedSharpAdbClient.Tests/AdbSocketTests.cs @@ -3,8 +3,6 @@ using System; using System.IO; using System.Text; -using System.Threading; -using System.Threading.Tasks; using Xunit; namespace AdvancedSharpAdbClient.Tests @@ -12,7 +10,7 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class AdbSocketTests + public partial class AdbSocketTests { [Fact] public void CloseTest() @@ -49,19 +47,19 @@ public void IsOkayTest() } [Fact] - public void SendSyncRequestTest() => + public void SendSyncDATARequestTest() => RunTest( (socket) => socket.SendSyncRequest(SyncCommand.DATA, 2), new byte[] { (byte)'D', (byte)'A', (byte)'T', (byte)'A', 2, 0, 0, 0 }); [Fact] - public void SendSyncRequestTest2() => + public void SendSyncSENDRequestTest() => RunTest( (socket) => socket.SendSyncRequest(SyncCommand.SEND, "/test"), new byte[] { (byte)'S', (byte)'E', (byte)'N', (byte)'D', 5, 0, 0, 0, (byte)'/', (byte)'t', (byte)'e', (byte)'s', (byte)'t' }); [Fact] - public void SendSyncRequestTest3() => + public void SendSyncDENTRequestTest() => RunTest( (socket) => socket.SendSyncRequest(SyncCommand.DENT, "/data", 633), new byte[] { (byte)'D', (byte)'E', (byte)'N', (byte)'T', 9, 0, 0, 0, (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)',', (byte)'6', (byte)'3', (byte)'3' }); @@ -104,24 +102,6 @@ public void ReadSyncString() Assert.Equal("Hello", socket.ReadSyncString()); } - [Fact] - public async Task ReadStringAsyncTest() - { - DummyTcpSocket tcpSocket = new(); - AdbSocket socket = new(tcpSocket); - - using (BinaryWriter writer = new(tcpSocket.InputStream, Encoding.ASCII, true)) - { - writer.Write(Encoding.ASCII.GetBytes(5.ToString("X4"))); - writer.Write(Encoding.ASCII.GetBytes("Hello")); - writer.Flush(); - } - - tcpSocket.InputStream.Position = 0; - - Assert.Equal("Hello", await socket.ReadStringAsync(CancellationToken.None)); - } - [Fact] public void ReadAdbOkayResponseTest() { @@ -189,35 +169,6 @@ public void ReadTest() Assert.Equal(0, received[100]); } - [Fact] - public async Task ReadAsyncTest() - { - DummyTcpSocket tcpSocket = new(); - AdbSocket socket = new(tcpSocket); - - // Read 100 bytes from a stream which has 101 bytes available - byte[] data = new byte[101]; - for (int i = 0; i < 101; i++) - { - data[i] = (byte)i; - } - - tcpSocket.InputStream.Write(data, 0, 101); - tcpSocket.InputStream.Position = 0; - - // Buffer has a capacity of 101, but we'll only want to read 100 bytes - byte[] received = new byte[101]; - - await socket.ReadAsync(received, 100, CancellationToken.None); - - for (int i = 0; i < 100; i++) - { - Assert.Equal(received[i], (byte)i); - } - - Assert.Equal(0, received[100]); - } - [Fact] public void SendAdbRequestTest() => RunTest( diff --git a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj index 65e25fa1..4a3ef916 100644 --- a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj +++ b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj @@ -23,6 +23,9 @@ + + PreserveNewest + PreserveNewest diff --git a/AdvancedSharpAdbClient.Tests/AndroidProcessTests.cs b/AdvancedSharpAdbClient.Tests/AndroidProcessTests.cs deleted file mode 100644 index 54d24591..00000000 --- a/AdvancedSharpAdbClient.Tests/AndroidProcessTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) 2015 Quamotion. All rights reserved. -// -//----------------------------------------------------------------------- - -using AdvancedSharpAdbClient.DeviceCommands; -using Xunit; - -namespace AdvancedSharpAdbClient.Tests -{ - /// - /// Tests the class. - /// - public class AndroidProcessTests - { - /// - /// Tests the method. - /// - [Fact] - public void ParseTest() - { - string line = @"1 (init) S 0 0 0 0 -1 1077936384 1467 168323 0 38 12 141 863 249 20 0 1 0 4 2535424 245 4294967295 1 1 0 0 0 0 0 0 65536 4294967295 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"; - - AndroidProcess process = AndroidProcess.Parse(line); - - Assert.Equal(1, process.ProcessId); - Assert.Equal(0, process.ParentProcessId); - Assert.Equal(2535424ul, process.VirtualSize); - Assert.Equal(245, process.ResidentSetSize); - Assert.Equal(4294967295ul, process.WChan); - Assert.Equal(AndroidProcessState.S, process.State); - Assert.Equal("init", process.Name); - } - - [Fact] - public void ParseWithSpaceTest() - { - string line = @"194(irq/432-mdm sta) S 2 0 0 0 - 1 2130240 0 0 0 0 0 1 0 0 - 51 0 1 0 172 0 0 4294967295 0 0 0 0 0 0 0 2147483647 0 4294967295 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0"; - - AndroidProcess process = AndroidProcess.Parse(line); - - Assert.Equal(194, process.ProcessId); - Assert.Equal(2, process.ParentProcessId); - Assert.Equal(0ul, process.VirtualSize); - Assert.Equal(172, process.ResidentSetSize); - Assert.Equal(2147483647ul, process.WChan); - Assert.Equal(AndroidProcessState.S, process.State); - Assert.Equal("irq/432-mdm sta", process.Name); - } - - [Theory] - [InlineData("/init\01 (init) S 0 0 0 0 -1 1077952768 32422 176091 2066 1357 2116 957 1341 886 20 0 1 0 0 48603136 335 18446744073709551615 1 1 0 0 0 0 0 0 66560 0 0 0 17 3 0 0 34 0 0 0 0 0 0 0 0 0 0")] - [InlineData("10 (migration/0) S 2 0 0 0 -1 69238848 0 0 0 0 0 2651 0 0 -100 0 1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 99 1 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1054 (fsnotify_mark) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 71 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 5 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1075 (ecryptfs-kthrea) S 2 0 0 0 -1 2097216 0 0 0 0 2 23 0 0 20 0 1 0 71 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 5 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("11 (watchdog/0) S 2 0 0 0 -1 69239104 0 0 0 0 23 0 0 0 -100 0 1 0 4 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 99 1 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1154 (pcie_wq) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 75 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 4 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("11555 (irq/89-10430000) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 2804616 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 50 1 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("11556 (irq/90-10830000) S 2 0 0 0 -1 2129984 0 0 0 0 0 20 0 0 -51 0 1 0 2804616 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 50 1 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1156 (disp_det) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 76 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 7 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("12 (watchdog/1) S 2 0 0 0 -1 69239104 0 0 0 0 0 50 0 0 -100 0 1 0 4 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 99 1 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("com.android.bluetooth\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012020 (droid.bluetooth) S 3541 3541 0 0 -1 1077952832 233615 0 14408 0 2009 1518 0 0 20 0 61 0 14618 4045262848 29397 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 30 0 0 0 0 0 0 0 0 0 0")] - [InlineData("com.android.systemui\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012060 (ndroid.systemui) S 3541 3541 0 0 -1 1077952832 1927788 0 75927 0 30090 13049 0 0 20 0 73 0 14657 5656006656 65946 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 2 0 0 223 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1210 (hwrng) S 2 0 0 0 -1 2097216 0 0 0 0 0 1310 0 0 20 0 1 0 77 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 2 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1212 (g3d_dvfs) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 77 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("webview_zygote\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012122 (webview_zygote) S 3542 3541 0 0 -1 4211008 20885 1118065 1825 46464 17 72 21524 8739 20 0 5 0 14690 1686634496 8058 18446744073709551615 1 1 0 0 0 0 4612 1 1073841400 0 0 0 17 4 0 0 1 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1214 (kbase_job_fault) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 77 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1218 (coagent1) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 78 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 4 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("/vendor/bin/hw/wpa_supplicant\0-O/data/vendor/wifi/wpa/sockets\0-puse_p2p_group_interface=1\0-g@android:wpa_wlan0\012195 (wpa_supplicant) S 1 12195 0 0 -1 4210944 15790 0 976 0 319 1722 0 0 20 0 1 0 14726 2186141696 1492 18446744073709551615 1 1 0 0 0 0 0 0 1073792251 0 0 0 17 2 0 0 29 0 0 0 0 0 0 0 0 0 0")] - [InlineData("com.android.systemui:InfinityWallpaper\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012213 (finityWallpaper) S 3541 3541 0 0 -1 1077952832 245384 0 9865 0 11873 10911 0 0 20 0 26 0 14741 4492955648 31569 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 2 0 0 20 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1222 (irq/286-muic-ir) S 2 0 0 0 -1 2129984 0 0 0 0 0 8 0 0 -51 0 1 0 78 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("com.sec.location.nsflp2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012240 (location.nsflp2) S 3541 3541 0 0 -1 1077952832 175058 0 11267 0 584 349 0 0 20 0 21 0 14775 3977629696 26643 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 4 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1226 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1227 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1228 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1229 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1230 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] - [InlineData("1869 (irq/306-(null)) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 116 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 50 1 0 0 0 0 0 0 0 0 0 0 0")] - - public void ParseTests(string line) => _ = AndroidProcess.Parse(line, true); - } -} diff --git a/AdvancedSharpAdbClient.Tests/Assets/dumpscreen.txt b/AdvancedSharpAdbClient.Tests/Assets/dumpscreen.txt new file mode 100644 index 00000000..1b1cd035 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/Assets/dumpscreen.txt @@ -0,0 +1 @@ +UI hierchary dumped to: /dev/tty \ No newline at end of file diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs new file mode 100644 index 00000000..96ede1e1 --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.Async.cs @@ -0,0 +1,356 @@ +using AdvancedSharpAdbClient.Tests; +using Moq; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AdvancedSharpAdbClient.DeviceCommands.Tests +{ + public partial class DeviceExtensionsTests + { + [Fact] + public void StatAsyncTest() + { + FileStatistics stats = new(); + TaskCompletionSource tcs = new(); + tcs.SetResult(stats); + + Mock client = new(); + Mock mock = new(); + mock.Setup(m => m.StatAsync("/test", It.IsAny())).Returns(tcs.Task); + + lock (FactoriesTests.locker) + { + Factories.SyncServiceFactory = (c, d) => mock.Object; + + DeviceData device = new(); + Assert.Equal(tcs.Task.Result, client.Object.StatAsync(device, "/test").Result); + + Factories.Reset(); + } + } + + [Fact] + public async void GetEnvironmentVariablesAsyncTest() + { + DummyAdbClient adbClient = new(); + + adbClient.Commands[EnvironmentVariablesReceiver.PrintEnvCommand] = "a=b"; + + DeviceData device = new(); + + Dictionary variables = await adbClient.GetEnvironmentVariablesAsync(device); + Assert.NotNull(variables); + Assert.Single(variables.Keys); + Assert.True(variables.ContainsKey("a")); + Assert.Equal("b", variables["a"]); + } + + [Fact] + public async void UninstallPackageAsyncTests() + { + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = ""; + adbClient.Commands["pm uninstall com.example"] = ""; + + DeviceData device = new() + { + State = DeviceState.Online + }; + await adbClient.UninstallPackageAsync(device, "com.example"); + + Assert.Equal(2, adbClient.ReceivedCommands.Count); + Assert.Equal("pm list packages -f", adbClient.ReceivedCommands[0]); + Assert.Equal("pm uninstall com.example", adbClient.ReceivedCommands[1]); + } + + [Theory] + [InlineData(@"Activity Resolver Table: + Non-Data Actions: + com.android.providers.contacts.DUMP_DATABASE: + 310a0bd8 com.android.providers.contacts/.debug.ContactsDumpActivity + +Receiver Resolver Table: + Schemes: + package: + 31f30b31 com.android.providers.contacts/.PackageIntentReceiver (4 filters) + +Registered ContentProviders: + com.android.providers.contacts/.debug.DumpFileProvider: + Provider{2b000d84 com.android.providers.contacts/.debug.DumpFileProvider} + +ContentProvider Authorities: + [com.android.voicemail]: + Provider{316ea633 com.android.providers.contacts/.VoicemailContentProvider} + applicationInfo=ApplicationInfo{1327df0 com.android.providers.contacts} + +Key Set Manager: + [com.android.providers.contacts] + Signing KeySets: 3 + +Packages: + Package [com.android.providers.contacts] (3d5205d5): + versionCode=22 targetSdk=22 + versionName=5.1-eng.buildbot.20151117.204057 + splits=[base] + +Shared users: + SharedUser [android.uid.shared] (3341dee): + userId=10002 gids=[3003, 1028, 1015] + grantedPermissions: + android.permission.WRITE_SETTINGS", 22, "5.1-eng.buildbot.20151117.204057", "com.example")] + [InlineData(@"Activity Resolver Table: + Schemes: + package: + 423fa100 jp.co.cyberagent.stf/.IconActivity filter 427ae628 + + Non-Data Actions: + jp.co.cyberagent.stf.ACTION_IDENTIFY: + 423fa4d8 jp.co.cyberagent.stf/.IdentityActivity filter 427c76a8 + +Service Resolver Table: + Non-Data Actions: + jp.co.cyberagent.stf.ACTION_STOP: + 423fc3d8 jp.co.cyberagent.stf/.Service filter 427e4ca8 + jp.co.cyberagent.stf.ACTION_START: + 423fc3d8 jp.co.cyberagent.stf/.Service filter 427e4ca8 + +Packages: + Package [jp.co.cyberagent.stf] (428c8c10): + userId=10153 gids=[3003, 1015, 1023, 1028] + sharedUser=null + pkg=Package{42884220 jp.co.cyberagent.stf} + codePath=/data/app/jp.co.cyberagent.stf-1.apk + resourcePath=/data/app/jp.co.cyberagent.stf-1.apk + nativeLibraryPath=/data/app-lib/jp.co.cyberagent.stf-1 + versionCode=4 + applicationInfo=ApplicationInfo{4287f2e0 jp.co.cyberagent.stf} + flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] + versionName=2.1.0 + dataDir=/data/data/jp.co.cyberagent.stf + targetSdk=22 + supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity] + timeStamp=2017-09-08 15:52:21 + firstInstallTime=2017-09-08 15:52:21 + lastUpdateTime=2017-09-08 15:52:21 + signatures=PackageSignatures{419a7e60 [41bb3628]} + permissionsFixed=true haveGids=true installStatus=1 + pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] + packageOnlyForOwnerUser: false + componentsOnlyForOwerUser: + User 0: installed=true stopped=true notLaunched=true enabled=0 + grantedPermissions: + android.permission.READ_EXTERNAL_STORAGE + android.permission.READ_PHONE_STATE + android.permission.DISABLE_KEYGUARD + android.permission.WRITE_EXTERNAL_STORAGE + android.permission.INTERNET + android.permission.CHANGE_WIFI_STATE + android.permission.MANAGE_ACCOUNTS + android.permission.ACCESS_WIFI_STATE + android.permission.GET_ACCOUNTS + android.permission.ACCESS_NETWORK_STATE + android.permission.WAKE_LOCK +mPackagesOnlyForOwnerUser: + package : com.android.mms + package : com.android.phone + package : com.sec.knox.containeragent +mComponentsOnlyForOwnerUser: + package : com.android.contacts + cmp : com.android.contacts.activities.DialtactsActivity + +mEnforceCopyingLibPackages: + +mSkippingApks: + +mSettings.mPackages: +the number of packages is 223 +mPackages: +the number of packages is 223 +End!!!!", 4, "2.1.0", "jp.co.cyberagent.stf")] + [InlineData(@"Activity Resolver Table: + Schemes: + package: + de681a8 jp.co.cyberagent.stf/.IconActivity filter 2863eca + Action: ""jp.co.cyberagent.stf.ACTION_ICON"" + Category: ""android.intent.category.DEFAULT"" + Scheme: ""package"" + + Non-Data Actions: + jp.co.cyberagent.stf.ACTION_IDENTIFY: + 69694c1 jp.co.cyberagent.stf/.IdentityActivity filter 30bda35 + Action: ""jp.co.cyberagent.stf.ACTION_IDENTIFY"" + Category: ""android.intent.category.DEFAULT"" + +Service Resolver Table: + Non-Data Actions: + jp.co.cyberagent.stf.ACTION_STOP: + db65466 jp.co.cyberagent.stf/.Service filter 7c0646c + Action: ""jp.co.cyberagent.stf.ACTION_START"" + Action: ""jp.co.cyberagent.stf.ACTION_STOP"" + Category: ""android.intent.category.DEFAULT"" + jp.co.cyberagent.stf.ACTION_START: + db65466 jp.co.cyberagent.stf/.Service filter 7c0646c + Action: ""jp.co.cyberagent.stf.ACTION_START"" + Action: ""jp.co.cyberagent.stf.ACTION_STOP"" + Category: ""android.intent.category.DEFAULT"" + +Key Set Manager: + [jp.co.cyberagent.stf] + Signing KeySets: 57 + +Packages: + Package [jp.co.cyberagent.stf] (13d33a7): + userId=11261 + pkg=Package{6f61054 jp.co.cyberagent.stf} + codePath=/data/app/jp.co.cyberagent.stf-Q3jXaNJMy6AIVndbPuclbg== + resourcePath=/data/app/jp.co.cyberagent.stf-Q3jXaNJMy6AIVndbPuclbg== + legacyNativeLibraryDir=/data/app/jp.co.cyberagent.stf-Q3jXaNJMy6AIVndbPuclbg==/lib + primaryCpuAbi=null + secondaryCpuAbi=null + versionCode=4 minSdk=9 targetSdk=22 + versionName=2.1.0 + splits=[base] + apkSigningVersion=2 + applicationInfo=ApplicationInfo{4b6bbfd jp.co.cyberagent.stf} + flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] + dataDir=/data/user/0/jp.co.cyberagent.stf + supportsScreens=[small, medium, large, xlarge, resizeable, anyDensity] + timeStamp=2017-09-08 22:06:05 + firstInstallTime=2017-09-08 22:06:07 + lastUpdateTime=2017-09-08 22:06:07 + signatures=PackageSignatures{1c350f2 [37b7ecb5]} + installPermissionsFixed=true installStatus=1 + pkgFlags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] + requested permissions: + android.permission.DISABLE_KEYGUARD + android.permission.READ_PHONE_STATE + android.permission.WAKE_LOCK + android.permission.INTERNET + android.permission.ACCESS_NETWORK_STATE + android.permission.WRITE_EXTERNAL_STORAGE + android.permission.GET_ACCOUNTS + android.permission.MANAGE_ACCOUNTS + android.permission.CHANGE_WIFI_STATE + android.permission.ACCESS_WIFI_STATE + android.permission.READ_EXTERNAL_STORAGE + install permissions: + android.permission.MANAGE_ACCOUNTS: granted=true + android.permission.INTERNET: granted=true + android.permission.READ_EXTERNAL_STORAGE: granted=true + android.permission.READ_PHONE_STATE: granted=true + android.permission.CHANGE_WIFI_STATE: granted=true + android.permission.ACCESS_NETWORK_STATE: granted=true + android.permission.DISABLE_KEYGUARD: granted=true + android.permission.GET_ACCOUNTS: granted=true + android.permission.WRITE_EXTERNAL_STORAGE: granted=true + android.permission.ACCESS_WIFI_STATE: granted=true + android.permission.WAKE_LOCK: granted=true + User 0: ceDataInode=409220 installed=true hidden=false suspended=false stopped=true notLaunched=true enabled=0 instant=false + gids=[3003] + runtime permissions: + User 10: ceDataInode=0 installed=true hidden=false suspended=false stopped=true notLaunched=true enabled=0 instant=false + gids=[3003] + runtime permissions: + +Package Changes: + Sequence number=45 + User 0: + seq=6, package=com.google.android.gms + seq=9, package=be.brusselsairport.appyflight + seq=11, package=com.android.vending + seq=13, package=app.qrcode + seq=15, package=com.android.chrome + seq=16, package=com.google.android.apps.docs + seq=17, package=com.google.android.inputmethod.latin + seq=18, package=com.google.android.music + seq=20, package=com.google.android.apps.walletnfcrel + seq=21, package=com.google.android.youtube + seq=22, package=com.google.android.calendar + seq=44, package=jp.co.cyberagent.stf + User 10: + seq=10, package=com.android.vending + seq=14, package=com.google.android.apps.walletnfcrel + seq=15, package=com.android.chrome + seq=16, package=com.google.android.apps.docs + seq=17, package=com.google.android.inputmethod.latin + seq=18, package=com.google.android.music + seq=19, package=com.google.android.youtube + seq=22, package=com.google.android.calendar + seq=44, package=jp.co.cyberagent.stf + + +Dexopt state: + [jp.co.cyberagent.stf] + Instruction Set: arm64 + path: /data/app/jp.co.cyberagent.stf-Q3jXaNJMy6AIVndbPuclbg==/base.apk + status: /data/app/jp.co.cyberagent.stf-Q3jXaNJMy6AIVndbPuclbg==/oat/arm64/base.odex[status=kOatUpToDate, compilati + on_filter=quicken] + + +Compiler stats: + [jp.co.cyberagent.stf] + base.apk - 1084", 4, "2.1.0", "jp.co.cyberagent.stf")] + public async void GetPackageVersionAsyncTest(string command, int versionCode, string versionName, string packageName) + { + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = ""; + adbClient.Commands[$"dumpsys package {packageName}"] = command; + + DeviceData device = new() + { + State = DeviceState.Online + }; + VersionInfo version = await adbClient.GetPackageVersionAsync(device, packageName); + + Assert.Equal(versionCode, version.VersionCode); + Assert.Equal(versionName, version.VersionName); + + Assert.Equal(2, adbClient.ReceivedCommands.Count); + Assert.Equal("pm list packages -f", adbClient.ReceivedCommands[0]); + Assert.Equal($"dumpsys package {packageName}", adbClient.ReceivedCommands[1]); + } + + [Fact] + public async void ListProcessesAsyncTest() + { + DummyAdbClient adbClient = new(); + + adbClient.Commands[@"SDK=""$(/system/bin/getprop ro.build.version.sdk)"" +if [ $SDK -lt 24 ] +then + /system/bin/ls /proc/ +else + /system/bin/ls -1 /proc/ +fi".Replace("\r\n", "\n")] = +@"1 +2 +3 +acpi +asound"; + adbClient.Commands["cat /proc/1/stat /proc/2/stat /proc/3/stat "] = +@"1 (init) S 0 0 0 0 -1 1077944576 2680 83280 0 179 0 67 16 39 20 0 1 0 2 17735680 143 18446744073709551615 134512640 135145076 4288071392 4288070744 134658736 0 0 0 65536 18446744071580117077 0 0 17 1 0 0 0 0 0 135152736 135165080 142131200 4288073690 4288073696 4288073696 4288073714 0 +2 (kthreadd) S 0 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579254310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +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"; + adbClient.Commands["cat /proc/1/cmdline /proc/1/stat /proc/2/cmdline /proc/2/stat /proc/3/cmdline /proc/3/stat "] = +@" +1 (init) S 0 0 0 0 -1 1077944576 2680 83280 0 179 0 67 16 39 20 0 1 0 2 17735680 143 18446744073709551615 134512640 135145076 4288071392 4288070744 134658736 0 0 0 65536 18446744071580117077 0 0 17 1 0 0 0 0 0 135152736 135165080 142131200 4288073690 4288073696 4288073696 4288073714 0 + +2 (kthreadd) S 0 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579254310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +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 = (await adbClient.ListProcessesAsync(device)).ToArray(); + + Assert.Equal(3, processes.Length); + Assert.Equal("init", processes[0].Name); + Assert.Equal("kthreadd", processes[1].Name); + Assert.Equal("ksoftirqd/0", processes[2].Name); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs index 46e202e9..a7527828 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/DeviceExtensionsTests.cs @@ -1,35 +1,27 @@ -using AdvancedSharpAdbClient.DeviceCommands; +using AdvancedSharpAdbClient.Tests; using Moq; using System.Collections.Generic; using System.Linq; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { /// /// Tests the class. /// - public class DeviceExtensionsTests + public partial class DeviceExtensionsTests { - public DeviceExtensionsTests() - { - lock (FactoriesTests.locker) - { - Factories.Reset(); - } - } - [Fact] public void StatTest() { - lock (FactoriesTests.locker) - { - FileStatistics stats = new(); + FileStatistics stats = new(); - Mock client = new(); - Mock mock = new(); - mock.Setup(m => m.Stat("/test")).Returns(stats); + Mock client = new(); + Mock mock = new(); + mock.Setup(m => m.Stat("/test")).Returns(stats); + lock (FactoriesTests.locker) + { Factories.SyncServiceFactory = (c, d) => mock.Object; DeviceData device = new(); @@ -44,7 +36,7 @@ public void GetEnvironmentVariablesTest() { DummyAdbClient adbClient = new(); - adbClient.Commands.Add(EnvironmentVariablesReceiver.PrintEnvCommand, "a=b"); + adbClient.Commands[EnvironmentVariablesReceiver.PrintEnvCommand] = "a=b"; DeviceData device = new(); @@ -60,8 +52,8 @@ public void UninstallPackageTests() { DummyAdbClient adbClient = new(); - adbClient.Commands.Add("pm list packages -f", ""); - adbClient.Commands.Add("pm uninstall com.example", ""); + adbClient.Commands["pm list packages -f"] = ""; + adbClient.Commands["pm uninstall com.example"] = ""; DeviceData device = new() { @@ -74,14 +66,8 @@ public void UninstallPackageTests() Assert.Equal("pm uninstall com.example", adbClient.ReceivedCommands[1]); } - [Fact] - public void GetPackageVersionTest() - { - DummyAdbClient adbClient = new(); - - adbClient.Commands.Add("pm list packages -f", ""); - adbClient.Commands.Add("dumpsys package com.example", -@"Activity Resolver Table: + [Theory] + [InlineData(@"Activity Resolver Table: Non-Data Actions: com.android.providers.contacts.DUMP_DATABASE: 310a0bd8 com.android.providers.contacts/.debug.ContactsDumpActivity @@ -114,30 +100,8 @@ Package [com.android.providers.contacts] (3d5205d5): SharedUser [android.uid.shared] (3341dee): userId=10002 gids=[3003, 1028, 1015] grantedPermissions: - android.permission.WRITE_SETTINGS"); - - DeviceData device = new() - { - State = DeviceState.Online - }; - VersionInfo version = adbClient.GetPackageVersion(device, "com.example"); - - Assert.Equal(22, version.VersionCode); - Assert.Equal("5.1-eng.buildbot.20151117.204057", version.VersionName); - - Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal("dumpsys package com.example", adbClient.ReceivedCommands[1]); - } - - [Fact] - public void GetPackageVersionTest2() - { - DummyAdbClient adbClient = new(); - - adbClient.Commands.Add("pm list packages -f", ""); - adbClient.Commands.Add("dumpsys package jp.co.cyberagent.stf", -@"Activity Resolver Table: + android.permission.WRITE_SETTINGS", 22, "5.1-eng.buildbot.20151117.204057", "com.example")] + [InlineData(@"Activity Resolver Table: Schemes: package: 423fa100 jp.co.cyberagent.stf/.IconActivity filter 427ae628 @@ -205,30 +169,8 @@ Package [jp.co.cyberagent.stf] (428c8c10): the number of packages is 223 mPackages: the number of packages is 223 -End!!!!"); - - DeviceData device = new() - { - State = DeviceState.Online - }; - VersionInfo version = adbClient.GetPackageVersion(device, "jp.co.cyberagent.stf"); - - Assert.Equal(4, version.VersionCode); - Assert.Equal("2.1.0", version.VersionName); - - Assert.Equal(2, adbClient.ReceivedCommands.Count); - Assert.Equal("pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal("dumpsys package jp.co.cyberagent.stf", adbClient.ReceivedCommands[1]); - } - - [Fact] - public void GetPackageVersionTest3() - { - DummyAdbClient adbClient = new(); - - adbClient.Commands.Add("pm list packages -f", ""); - adbClient.Commands.Add("dumpsys package jp.co.cyberagent.stf", -@"Activity Resolver Table: +End!!!!", 4, "2.1.0", "jp.co.cyberagent.stf")] + [InlineData(@"Activity Resolver Table: Schemes: package: de681a8 jp.co.cyberagent.stf/.IconActivity filter 2863eca @@ -350,20 +292,26 @@ Package [jp.co.cyberagent.stf] (13d33a7): Compiler stats: [jp.co.cyberagent.stf] - base.apk - 1084"); + base.apk - 1084", 4, "2.1.0", "jp.co.cyberagent.stf")] + public void GetPackageVersionTest(string command, int versionCode, string versionName, string packageName) + { + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = ""; + adbClient.Commands[$"dumpsys package {packageName}"] = command; DeviceData device = new() { State = DeviceState.Online }; - VersionInfo version = adbClient.GetPackageVersion(device, "jp.co.cyberagent.stf"); + VersionInfo version = adbClient.GetPackageVersion(device, packageName); - Assert.Equal(4, version.VersionCode); - Assert.Equal("2.1.0", version.VersionName); + Assert.Equal(versionCode, version.VersionCode); + Assert.Equal(versionName, version.VersionName); Assert.Equal(2, adbClient.ReceivedCommands.Count); Assert.Equal("pm list packages -f", adbClient.ReceivedCommands[0]); - Assert.Equal("dumpsys package jp.co.cyberagent.stf", adbClient.ReceivedCommands[1]); + Assert.Equal($"dumpsys package {packageName}", adbClient.ReceivedCommands[1]); } [Fact] @@ -371,29 +319,29 @@ public void ListProcessesTest() { DummyAdbClient adbClient = new(); - adbClient.Commands.Add(@"SDK=""$(/system/bin/getprop ro.build.version.sdk)"" + adbClient.Commands[@"SDK=""$(/system/bin/getprop ro.build.version.sdk)"" if [ $SDK -lt 24 ] then /system/bin/ls /proc/ else /system/bin/ls -1 /proc/ -fi".Replace("\r\n", "\n"), +fi".Replace("\r\n", "\n")] = @"1 2 3 acpi -asound"); - adbClient.Commands.Add("cat /proc/1/stat /proc/2/stat /proc/3/stat ", +asound"; + adbClient.Commands["cat /proc/1/stat /proc/2/stat /proc/3/stat "] = @"1 (init) S 0 0 0 0 -1 1077944576 2680 83280 0 179 0 67 16 39 20 0 1 0 2 17735680 143 18446744073709551615 134512640 135145076 4288071392 4288070744 134658736 0 0 0 65536 18446744071580117077 0 0 17 1 0 0 0 0 0 135152736 135165080 142131200 4288073690 4288073696 4288073696 4288073714 0 2 (kthreadd) S 0 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579254310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -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"); - adbClient.Commands.Add("cat /proc/1/cmdline /proc/1/stat /proc/2/cmdline /proc/2/stat /proc/3/cmdline /proc/3/stat ", +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"; + adbClient.Commands["cat /proc/1/cmdline /proc/1/stat /proc/2/cmdline /proc/2/stat /proc/3/cmdline /proc/3/stat "] = @" 1 (init) S 0 0 0 0 -1 1077944576 2680 83280 0 179 0 67 16 39 20 0 1 0 2 17735680 143 18446744073709551615 134512640 135145076 4288071392 4288070744 134658736 0 0 0 65536 18446744071580117077 0 0 17 1 0 0 0 0 0 135152736 135165080 142131200 4288073690 4288073696 4288073696 4288073714 0 2 (kthreadd) S 0 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579254310 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -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"); +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(); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs index bcaefbb6..77723e5e 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/LinuxPathTests.cs @@ -1,8 +1,7 @@ -using AdvancedSharpAdbClient.DeviceCommands; -using System; +using System; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { /// /// Tests the class. @@ -18,23 +17,18 @@ public void CheckInvalidPathCharsTest() => // Should not throw an exception. LinuxPath.CheckInvalidPathChars("/var/test"); - [Fact] - public void CheckInvalidPathCharsTest2() => - // Should throw an exception. - _ = Assert.Throws(() => LinuxPath.CheckInvalidPathChars("/var/test > out")); - - [Fact] - public void CheckInvalidPathCharsTest3() => + [Theory] + [InlineData("/var/test > out")] + [InlineData("\t/var/test")] + public void CheckInvalidPathCharsThrowTest(string path) => // Should throw an exception. - _ = Assert.Throws(() => LinuxPath.CheckInvalidPathChars("\t/var/test")); + _ = Assert.Throws(() => LinuxPath.CheckInvalidPathChars(path)); - [Fact] - public void CombineNullTest() => - _ = Assert.Throws(() => LinuxPath.Combine(null)); - - [Fact] - public void CombineNullTest2() => - _ = Assert.Throws(() => LinuxPath.Combine(new string[] { "/test", "hi", null })); + [Theory] + [InlineData(null)] + [InlineData("/test", "hi", null)] + public void CombineNullTest(params string[] paths) => + _ = Assert.Throws(() => LinuxPath.Combine(paths)); [Fact] public void CombineTest() diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs index 5f695116..5749e36a 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/AndroidProcessTests.cs @@ -1,49 +1,110 @@ -using AdvancedSharpAdbClient.DeviceCommands; +//----------------------------------------------------------------------- +// +// Copyright (c) 2015 Quamotion. All rights reserved. +// +//----------------------------------------------------------------------- + using System; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { /// /// Tests the class. /// public class AndroidProcessTests { - [Fact] - public void ParseNullTest() => - _ = Assert.Throws(() => AndroidProcess.Parse(null)); - - [Fact] - public void ParseTooFewPartsTest() => - _ = Assert.Throws(() => AndroidProcess.Parse("1 (init) S 0 0 0 0 -1 1077944576 2680 83280 0 179 0 67 16 39 20 0 1 0 2 17735680 143 18446744073709551615 134512640 135145076 ")); - /// - /// Tests the parsing of a process where the /cmdline output is prefixed. + /// Tests the method. /// [Fact] public void ParseTest() { - AndroidProcess p = AndroidProcess.Parse("/init\0--second-stage\01 (init) S 0 0 0 0 -1 1077944576 5343 923316 0 1221 2 48 357 246 20 0 1 0 3 24002560 201 18446744073709551615 134512640 135874648 4293296896 4293296356 135412421 0 0 0 65536 18446744071580341721 0 0 17 3 0 0 0 0 0 135882176 135902816 152379392 4293300171 4293300192 4293300192 4293300210 0", true); - Assert.Equal("/init", p.Name); + string line = @"1 (init) S 0 0 0 0 -1 1077936384 1467 168323 0 38 12 141 863 249 20 0 1 0 4 2535424 245 4294967295 1 1 0 0 0 0 0 0 65536 4294967295 0 0 17 3 0 0 0 0 0 0 0 0 0 0 0 0 0"; + + AndroidProcess process = AndroidProcess.Parse(line); + + Assert.Equal(1, process.ProcessId); + Assert.Equal(0, process.ParentProcessId); + Assert.Equal(2535424ul, process.VirtualSize); + Assert.Equal(245, process.ResidentSetSize); + Assert.Equal(4294967295ul, process.WChan); + Assert.Equal(AndroidProcessState.S, process.State); + Assert.Equal("init", process.Name); } - /// - /// Tests the parsing of a process where the /cmdline output is empty. - /// [Fact] - public void ParseTest2() + public void ParseLongTest() { AndroidProcess p = AndroidProcess.Parse("10 (rcu_sched) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 9 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579565281 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0", true); Assert.Equal("rcu_sched", p.Name); } + /// + /// Tests the parsing of a process where the cmd line output is empty. + /// [Fact] - public void ParseTest3() + public void ParseWithEmptyTest() { AndroidProcess p = AndroidProcess.Parse("be.xx.yy.android.test\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\04212 (le.android.test) S 2088 2088 0 0 -1 1077944640 10251 1315 2 0 10 8 0 1 20 0 10 0 15838 1062567936 12163 18446744073709551615 4152340480 4152354824 4289177024 4289174228 4147921093 0 4612 0 38136 18446744073709551615 0 0 17 1 0 0 0 0 0 4152360256 4152360952 4157476864 4289182806 4289182882 4289182882 4289183712 0", true); Assert.Equal("be.xx.yy.android.test", p.Name); } + [Fact] + public void ParseNullTest() => + _ = Assert.Throws(() => AndroidProcess.Parse(null)); + + [Fact] + public void ParseTooFewPartsTest() => + _ = Assert.Throws(() => AndroidProcess.Parse("1 (init) S 0 0 0 0 -1 1077944576 2680 83280 0 179 0 67 16 39 20 0 1 0 2 17735680 143 18446744073709551615 134512640 135145076 ")); + + [Fact] + public void ParseWithSpaceTest() + { + string line = @"194(irq/432-mdm sta) S 2 0 0 0 - 1 2130240 0 0 0 0 0 1 0 0 - 51 0 1 0 172 0 0 4294967295 0 0 0 0 0 0 0 2147483647 0 4294967295 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0"; + + AndroidProcess process = AndroidProcess.Parse(line); + + Assert.Equal(194, process.ProcessId); + Assert.Equal(2, process.ParentProcessId); + Assert.Equal(0ul, process.VirtualSize); + Assert.Equal(172, process.ResidentSetSize); + Assert.Equal(2147483647ul, process.WChan); + Assert.Equal(AndroidProcessState.S, process.State); + Assert.Equal("irq/432-mdm sta", process.Name); + } + + [Theory] + [InlineData("/init\01 (init) S 0 0 0 0 -1 1077952768 32422 176091 2066 1357 2116 957 1341 886 20 0 1 0 0 48603136 335 18446744073709551615 1 1 0 0 0 0 0 0 66560 0 0 0 17 3 0 0 34 0 0 0 0 0 0 0 0 0 0")] + [InlineData("10 (migration/0) S 2 0 0 0 -1 69238848 0 0 0 0 0 2651 0 0 -100 0 1 0 2 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 99 1 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1054 (fsnotify_mark) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 71 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 5 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1075 (ecryptfs-kthrea) S 2 0 0 0 -1 2097216 0 0 0 0 2 23 0 0 20 0 1 0 71 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 5 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("11 (watchdog/0) S 2 0 0 0 -1 69239104 0 0 0 0 23 0 0 0 -100 0 1 0 4 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 99 1 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1154 (pcie_wq) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 75 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 4 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("11555 (irq/89-10430000) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 2804616 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 50 1 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("11556 (irq/90-10830000) S 2 0 0 0 -1 2129984 0 0 0 0 0 20 0 0 -51 0 1 0 2804616 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 50 1 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1156 (disp_det) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 76 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 7 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("12 (watchdog/1) S 2 0 0 0 -1 69239104 0 0 0 0 0 50 0 0 -100 0 1 0 4 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 99 1 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("com.android.bluetooth\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012020 (droid.bluetooth) S 3541 3541 0 0 -1 1077952832 233615 0 14408 0 2009 1518 0 0 20 0 61 0 14618 4045262848 29397 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 30 0 0 0 0 0 0 0 0 0 0")] + [InlineData("com.android.systemui\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012060 (ndroid.systemui) S 3541 3541 0 0 -1 1077952832 1927788 0 75927 0 30090 13049 0 0 20 0 73 0 14657 5656006656 65946 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 2 0 0 223 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1210 (hwrng) S 2 0 0 0 -1 2097216 0 0 0 0 0 1310 0 0 20 0 1 0 77 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 2 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1212 (g3d_dvfs) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 77 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("webview_zygote\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012122 (webview_zygote) S 3542 3541 0 0 -1 4211008 20885 1118065 1825 46464 17 72 21524 8739 20 0 5 0 14690 1686634496 8058 18446744073709551615 1 1 0 0 0 0 4612 1 1073841400 0 0 0 17 4 0 0 1 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1214 (kbase_job_fault) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 77 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1218 (coagent1) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 78 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 4 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("/vendor/bin/hw/wpa_supplicant\0-O/data/vendor/wifi/wpa/sockets\0-puse_p2p_group_interface=1\0-g@android:wpa_wlan0\012195 (wpa_supplicant) S 1 12195 0 0 -1 4210944 15790 0 976 0 319 1722 0 0 20 0 1 0 14726 2186141696 1492 18446744073709551615 1 1 0 0 0 0 0 0 1073792251 0 0 0 17 2 0 0 29 0 0 0 0 0 0 0 0 0 0")] + [InlineData("com.android.systemui:InfinityWallpaper\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012213 (finityWallpaper) S 3541 3541 0 0 -1 1077952832 245384 0 9865 0 11873 10911 0 0 20 0 26 0 14741 4492955648 31569 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 2 0 0 20 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1222 (irq/286-muic-ir) S 2 0 0 0 -1 2129984 0 0 0 0 0 8 0 0 -51 0 1 0 78 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 1 50 1 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("com.sec.location.nsflp2\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\012240 (location.nsflp2) S 3541 3541 0 0 -1 1077952832 175058 0 11267 0 584 349 0 0 20 0 21 0 14775 3977629696 26643 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 4 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1226 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1227 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1228 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1229 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1230 (bioset) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 79 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 6 0 0 0 0 0 0 0 0 0 0 0 0 0")] + [InlineData("1869 (irq/306-(null)) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 -51 0 1 0 116 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 0 0 0 17 0 50 1 0 0 0 0 0 0 0 0 0 0 0")] + + public void ParseTests(string line) => _ = AndroidProcess.Parse(line, true); + [Fact] public void ToStringTest() { diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs new file mode 100644 index 00000000..9582f81c --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs @@ -0,0 +1,33 @@ +using Xunit; + +namespace AdvancedSharpAdbClient.DeviceCommands.Tests +{ + /// + /// Tests the class. + /// + public class VersionInfoTests + { + /// + /// Tests the method. + /// + [Theory] + [InlineData(1231, "1.2.3.1")] + [InlineData(9393, "9.3.9.3")] + [InlineData(12345432, "Version")] + [InlineData(098765456, "Unknown")] + public void DeconstructTest(int versionCode, string versionName) + { + VersionInfo version = new(versionCode, versionName); + (int code, string name) = version; + Assert.Equal(versionCode, code); + Assert.Equal(versionName, name); + } + + [Fact] + public void ToStringTest() + { + VersionInfo v = new(1234, "1.2.3.4"); + Assert.Equal("1.2.3.4 (1234)", v.ToString()); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs new file mode 100644 index 00000000..8f06edab --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.Async.cs @@ -0,0 +1,219 @@ +using AdvancedSharpAdbClient.Tests; +using System.IO; +using Xunit; + +namespace AdvancedSharpAdbClient.DeviceCommands.Tests +{ + public partial class PackageManagerTests + { + [Fact] + public async void InstallRemotePackageAsyncTest() + { + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install \"/data/test.apk\""] = string.Empty; + adbClient.Commands["pm install -r \"/data/test.apk\""] = string.Empty; + + DeviceData device = new() + { + State = DeviceState.Online + }; + + PackageManager manager = new(adbClient, device); + await manager.InstallRemotePackageAsync("/data/test.apk", false); + + Assert.Equal(2, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install \"/data/test.apk\"", adbClient.ReceivedCommands[1]); + + await manager.InstallRemotePackageAsync("/data/test.apk", true); + + Assert.Equal(3, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install -r \"/data/test.apk\"", adbClient.ReceivedCommands[2]); + } + + [Fact] + public void InstallPackageAsyncTest() + { + DummySyncService syncService = new(); + lock (FactoriesTests.locker) + { + Factories.SyncServiceFactory = (c, d) => syncService; + + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/test.txt\""] = string.Empty; + + DeviceData device = new() + { + State = DeviceState.Online + }; + + PackageManager manager = new(adbClient, device); + manager.InstallPackageAsync("Assets/test.txt", false).Wait(); + Assert.Equal(3, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[1]); + Assert.Equal("rm \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[2]); + + Assert.Single(syncService.UploadedFiles); + Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/test.txt")); + + Factories.Reset(); + } + } + + [Fact] + public async void InstallMultipleRemotePackageAsyncTest() + { + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install-create"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -r"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -r -p com.google.android.gms"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-write 936013062 base.apk \"/data/base.apk\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\""] = string.Empty; + adbClient.Commands["pm install-commit 936013062"] = string.Empty; + + DeviceData device = new() + { + State = DeviceState.Online + }; + + PackageManager manager = new(adbClient, device); + await manager.InstallMultipleRemotePackageAsync("/data/base.apk", new string[] { "/data/split-dpi.apk", "/data/split-abi.apk" }, false); + + Assert.Equal(6, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create", adbClient.ReceivedCommands[1]); + Assert.Equal("pm install-write 936013062 base.apk \"/data/base.apk\"", adbClient.ReceivedCommands[2]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[3]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[4]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[5]); + + await manager.InstallMultipleRemotePackageAsync("/data/base.apk", new string[] { "/data/split-dpi.apk", "/data/split-abi.apk" }, true); + + Assert.Equal(11, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -r", adbClient.ReceivedCommands[6]); + Assert.Equal("pm install-write 936013062 base.apk \"/data/base.apk\"", adbClient.ReceivedCommands[7]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[8]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[9]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[10]); + + await manager.InstallMultipleRemotePackageAsync(new string[] { "/data/split-dpi.apk", "/data/split-abi.apk" }, "com.google.android.gms", false); + + Assert.Equal(15, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[11]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[12]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[13]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[14]); + + await manager.InstallMultipleRemotePackageAsync(new string[] { "/data/split-dpi.apk", "/data/split-abi.apk" }, "com.google.android.gms", true); + + Assert.Equal(19, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -r -p com.google.android.gms", adbClient.ReceivedCommands[15]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[16]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[17]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[18]); + } + + [Fact] + public void InstallMultiplePackageAsyncTest() + { + DummySyncService syncService = new(); + lock (FactoriesTests.locker) + { + Factories.SyncServiceFactory = (c, d) => syncService; + + DummyAdbClient adbClient = new(); + + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install-create"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp0.apk \"/data/local/tmp/gapps.txt\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp1.apk \"/data/local/tmp/logcat.bin\""] = string.Empty; + adbClient.Commands["pm install-commit 936013062"] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/gapps.txt\""] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/logcat.bin\""] = string.Empty; + + DeviceData device = new() + { + State = DeviceState.Online + }; + + PackageManager manager = new(adbClient, device); + manager.InstallMultiplePackageAsync("Assets/test.txt", new string[] { "Assets/gapps.txt", "Assets/logcat.bin" }, false).Wait(); + Assert.Equal(9, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create", adbClient.ReceivedCommands[1]); + Assert.Equal("pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\"", adbClient.ReceivedCommands[2]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[3]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[4]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[5]); + Assert.Equal("rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[6]); + Assert.Equal("rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[7]); + Assert.Equal("rm \"/data/local/tmp/test.txt\"", 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")); + + syncService.UploadedFiles.Clear(); + manager.InstallMultiplePackageAsync(new string[] { "Assets/gapps.txt", "Assets/logcat.bin" }, "com.google.android.gms", false).Wait(); + Assert.Equal(15, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[9]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[10]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[11]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[12]); + Assert.Equal("rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[6]); + Assert.Equal("rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[7]); + + 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")); + + Factories.Reset(); + } + } + + [Fact] + public async void UninstallPackageAsyncTest() + { + DeviceData device = new() + { + State = DeviceState.Online + }; + + DummyAdbClient client = new(); + client.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + client.Commands["pm uninstall com.android.gallery3d"] = "Success"; + PackageManager manager = new(client, device); + + // Command should execute correctly; if the wrong command is passed an exception + // would be thrown. + await manager.UninstallPackageAsync("com.android.gallery3d"); + } + + [Fact] + public async void GetPackageVersionInfoAsyncTest() + { + DeviceData device = new() + { + State = DeviceState.Online + }; + + DummyAdbClient client = new(); + client.Commands["dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/gapps.txt"); + PackageManager manager = new(client, device, skipInit: true); + + VersionInfo versionInfo = await manager.GetVersionInfoAsync("com.google.android.gms"); + Assert.Equal(11062448, versionInfo.VersionCode); + Assert.Equal("11.0.62 (448-160311229)", versionInfo.VersionName); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs index 40916ea6..c8484d43 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/PackageManagerTests.cs @@ -1,13 +1,13 @@ -using AdvancedSharpAdbClient.DeviceCommands; +using AdvancedSharpAdbClient.Tests; using Moq; using System; using System.IO; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { - public class PackageManagerTests + public partial class PackageManagerTests { [Fact] public void ConstructorNullTest() @@ -17,8 +17,10 @@ public void ConstructorNullTest() _ = Assert.Throws(() => new PackageManager(Mock.Of(), null)); } - [Fact] - public void PackagesPropertyTest() + [Theory] + [InlineData("package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d", "com.android.gallery3d", "/system/app/Gallery2/Gallery2.apk")] + [InlineData("package:mwc2015.be", "mwc2015.be", null)] + public void PackagesPropertyTest(string command, string packageName, string path) { DeviceData device = new() { @@ -26,27 +28,11 @@ public void PackagesPropertyTest() }; DummyAdbClient client = new(); - client.Commands.Add("pm list packages -f", "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"); + client.Commands["pm list packages -f"] = command; PackageManager manager = new(client, device); - Assert.True(manager.Packages.ContainsKey("com.android.gallery3d")); - Assert.Equal("/system/app/Gallery2/Gallery2.apk", manager.Packages["com.android.gallery3d"]); - } - - [Fact] - public void PackagesPropertyTest2() - { - DeviceData device = new() - { - State = DeviceState.Online - }; - - DummyAdbClient client = new(); - client.Commands.Add("pm list packages -f", "package:mwc2015.be"); - PackageManager manager = new(client, device); - - Assert.True(manager.Packages.ContainsKey("mwc2015.be")); - Assert.Null(manager.Packages["mwc2015.be"]); + Assert.True(manager.Packages.ContainsKey(packageName)); + Assert.Equal(path, manager.Packages[packageName]); } [Fact] @@ -54,9 +40,9 @@ public void InstallRemotePackageTest() { DummyAdbClient adbClient = new(); - adbClient.Commands.Add("pm list packages -f", "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"); - adbClient.Commands.Add("pm install \"/data/test.apk\"", string.Empty); - adbClient.Commands.Add("pm install -r \"/data/test.apk\"", string.Empty); + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install \"/data/test.apk\""] = string.Empty; + adbClient.Commands["pm install -r \"/data/test.apk\""] = string.Empty; DeviceData device = new() { @@ -78,16 +64,16 @@ public void InstallRemotePackageTest() [Fact] public void InstallPackageTest() { + DummySyncService syncService = new(); lock (FactoriesTests.locker) { - DummySyncService syncService = new(); Factories.SyncServiceFactory = (c, d) => syncService; DummyAdbClient adbClient = new(); - adbClient.Commands.Add("pm list packages -f", "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"); - adbClient.Commands.Add("pm install \"/data/local/tmp/test.txt\"", string.Empty); - adbClient.Commands.Add("rm \"/data/local/tmp/test.txt\"", string.Empty); + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/test.txt\""] = string.Empty; DeviceData device = new() { @@ -112,13 +98,15 @@ public void InstallMultipleRemotePackageTest() { DummyAdbClient adbClient = new(); - adbClient.Commands.Add("pm list packages -f", "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"); - adbClient.Commands.Add("pm install-create", "Success: created install session [936013062]"); - adbClient.Commands.Add("pm install-create -r", "Success: created install session [936013062]"); - adbClient.Commands.Add("pm install-write 936013062 base.apk \"/data/base.apk\"", string.Empty); - adbClient.Commands.Add("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", string.Empty); - adbClient.Commands.Add("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", string.Empty); - adbClient.Commands.Add("pm install-commit 936013062", string.Empty); + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install-create"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -r"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -r -p com.google.android.gms"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-write 936013062 base.apk \"/data/base.apk\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\""] = string.Empty; + adbClient.Commands["pm install-commit 936013062"] = string.Empty ; DeviceData device = new() { @@ -143,27 +131,44 @@ public void InstallMultipleRemotePackageTest() Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[8]); Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[9]); Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[10]); + + manager.InstallMultipleRemotePackage(new string[] { "/data/split-dpi.apk", "/data/split-abi.apk" }, "com.google.android.gms", false); + + Assert.Equal(15, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[11]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[12]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[13]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[14]); + + manager.InstallMultipleRemotePackage(new string[] { "/data/split-dpi.apk", "/data/split-abi.apk" }, "com.google.android.gms", true); + + Assert.Equal(19, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -r -p com.google.android.gms", adbClient.ReceivedCommands[15]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/split-dpi.apk\"", adbClient.ReceivedCommands[16]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/split-abi.apk\"", adbClient.ReceivedCommands[17]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[18]); } [Fact] public void InstallMultiplePackageTest() { + DummySyncService syncService = new(); lock (FactoriesTests.locker) { - DummySyncService syncService = new(); Factories.SyncServiceFactory = (c, d) => syncService; DummyAdbClient adbClient = new(); - adbClient.Commands.Add("pm list packages -f", "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"); - adbClient.Commands.Add("pm install-create", "Success: created install session [936013062]"); - adbClient.Commands.Add("pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\"", string.Empty); - adbClient.Commands.Add("pm install-write 936013062 splitapp0.apk \"/data/local/tmp/gapps.txt\"", string.Empty); - adbClient.Commands.Add("pm install-write 936013062 splitapp1.apk \"/data/local/tmp/logcat.bin\"", string.Empty); - adbClient.Commands.Add("pm install-commit 936013062", string.Empty); - adbClient.Commands.Add("rm \"/data/local/tmp/test.txt\"", string.Empty); - adbClient.Commands.Add("rm \"/data/local/tmp/gapps.txt\"", string.Empty); - adbClient.Commands.Add("rm \"/data/local/tmp/logcat.bin\"", string.Empty); + adbClient.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + adbClient.Commands["pm install-create"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-create -p com.google.android.gms"] = "Success: created install session [936013062]"; + adbClient.Commands["pm install-write 936013062 base.apk \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp0.apk \"/data/local/tmp/gapps.txt\""] = string.Empty; + adbClient.Commands["pm install-write 936013062 splitapp1.apk \"/data/local/tmp/logcat.bin\""] = string.Empty; + adbClient.Commands["pm install-commit 936013062"] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/test.txt\""] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/gapps.txt\""] = string.Empty; + adbClient.Commands["rm \"/data/local/tmp/logcat.bin\""] = string.Empty; DeviceData device = new() { @@ -187,6 +192,20 @@ public void InstallMultiplePackageTest() Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/gapps.txt")); Assert.True(syncService.UploadedFiles.ContainsKey("/data/local/tmp/logcat.bin")); + syncService.UploadedFiles.Clear(); + manager.InstallMultiplePackage(new string[] { "Assets/gapps.txt", "Assets/logcat.bin" }, "com.google.android.gms", false); + Assert.Equal(15, adbClient.ReceivedCommands.Count); + Assert.Equal("pm install-create -p com.google.android.gms", adbClient.ReceivedCommands[9]); + Assert.Equal("pm install-write 936013062 splitapp0.apk \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[10]); + Assert.Equal("pm install-write 936013062 splitapp1.apk \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[11]); + Assert.Equal("pm install-commit 936013062", adbClient.ReceivedCommands[12]); + Assert.Equal("rm \"/data/local/tmp/gapps.txt\"", adbClient.ReceivedCommands[6]); + Assert.Equal("rm \"/data/local/tmp/logcat.bin\"", adbClient.ReceivedCommands[7]); + + 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")); + Factories.Reset(); } } @@ -200,8 +219,8 @@ public void UninstallPackageTest() }; DummyAdbClient client = new(); - client.Commands.Add("pm list packages -f", "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"); - client.Commands.Add("pm uninstall com.android.gallery3d", "Success"); + client.Commands["pm list packages -f"] = "package:/system/app/Gallery2/Gallery2.apk=com.android.gallery3d"; + client.Commands["pm uninstall com.android.gallery3d"] = "Success"; PackageManager manager = new(client, device); // Command should execute correctly; if the wrong command is passed an exception @@ -218,7 +237,7 @@ public void GetPackageVersionInfoTest() }; DummyAdbClient client = new(); - client.Commands.Add("dumpsys package com.google.android.gms", File.ReadAllText("Assets/gapps.txt")); + client.Commands["dumpsys package com.google.android.gms"] = File.ReadAllText("Assets/gapps.txt"); PackageManager manager = new(client, device, skipInit: true); VersionInfo versionInfo = manager.GetVersionInfo("com.google.android.gms"); diff --git a/AdvancedSharpAdbClient.Tests/Receivers/EnvironmentVariablesReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs similarity index 94% rename from AdvancedSharpAdbClient.Tests/Receivers/EnvironmentVariablesReceiverTests.cs rename to AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs index 9091380e..f37383d1 100644 --- a/AdvancedSharpAdbClient.Tests/Receivers/EnvironmentVariablesReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/EnvironmentVariablesReceiverTests.cs @@ -1,7 +1,6 @@ using Xunit; -using AdvancedSharpAdbClient.DeviceCommands; -namespace AdvancedSharpAdbClient.Tests +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs index eee798b3..ec9921e4 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/GetPropReceiverTests.cs @@ -1,8 +1,8 @@ -using AdvancedSharpAdbClient.DeviceCommands; +using AdvancedSharpAdbClient.Tests; using System.Collections.Generic; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { public class GetPropReceiverTests { @@ -15,9 +15,9 @@ public void ListPropertiesTest() }; DummyAdbClient client = new(); - client.Commands.Add("/system/bin/getprop", @"[init.svc.BGW]: [running] + client.Commands["/system/bin/getprop"] = @"[init.svc.BGW]: [running] [init.svc.MtkCodecService]: [running] -[init.svc.bootanim]: [stopped]"); +[init.svc.bootanim]: [stopped]"; Dictionary properties = client.GetProperties(device); Assert.NotNull(properties); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs similarity index 64% rename from AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallReceiverTests.cs rename to AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs index cb2a41c4..b0383e5b 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/InstallOutputReceiverTests.cs @@ -1,14 +1,13 @@ -using AdvancedSharpAdbClient.DeviceCommands; -using Xunit; +using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { - public class InstallReceiverTests + public class InstallOutputReceiverTests { [Fact] public void ProcessFailureTest() { - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); receiver.AddOutput("Failure [message]"); receiver.Flush(); @@ -19,29 +18,29 @@ public void ProcessFailureTest() [Fact] public void ProcessFailureEmptyMessageTest() { - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); receiver.AddOutput("Failure [ ]"); receiver.Flush(); Assert.False(receiver.Success); - Assert.Equal(InstallReceiver.UnknownError, receiver.ErrorMessage); + Assert.Equal(InstallOutputReceiver.UnknownError, receiver.ErrorMessage); } [Fact] public void ProcessFailureNoMessageTest() { - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); receiver.AddOutput("Failure"); receiver.Flush(); Assert.False(receiver.Success); - Assert.Equal(InstallReceiver.UnknownError, receiver.ErrorMessage); + Assert.Equal(InstallOutputReceiver.UnknownError, receiver.ErrorMessage); } [Fact] public void ProcessSuccessTest() { - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); receiver.AddOutput("Success"); receiver.Flush(); diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs index 588d195c..cf944bc2 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/PackageManagerReceiverTests.cs @@ -1,7 +1,7 @@ -using AdvancedSharpAdbClient.DeviceCommands; +using AdvancedSharpAdbClient.Tests; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { public class PackageManagerReceiverTests { diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs new file mode 100644 index 00000000..78c82b4c --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/ProcessOutputReceiverTests.cs @@ -0,0 +1,25 @@ +using Xunit; + +namespace AdvancedSharpAdbClient.DeviceCommands.Tests +{ + /// + /// Tests the class. + /// + public class ProcessOutputReceiverTests + { + [Fact] + public void ProcessOutputReceiverTest() + { + ProcessOutputReceiver receiver = new(); + receiver.AddOutput("/init\0--second-stage\01 (init) S 0 0 0 0 -1 1077944576 5343 923316 0 1221 2 48 357 246 20 0 1 0 3 24002560 201 18446744073709551615 134512640 135874648 4293296896 4293296356 135412421 0 0 0 65536 18446744071580341721 0 0 17 3 0 0 0 0 0 135882176 135902816 152379392 4293300171 4293300192 4293300192 4293300210 0"); + receiver.AddOutput("10 (rcu_sched) S 2 0 0 0 -1 2129984 0 0 0 0 0 0 0 0 20 0 1 0 9 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744071579565281 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0"); + receiver.AddOutput("be.xx.yy.android.test\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\04212 (le.android.test) S 2088 2088 0 0 -1 1077944640 10251 1315 2 0 10 8 0 1 20 0 10 0 15838 1062567936 12163 18446744073709551615 4152340480 4152354824 4289177024 4289174228 4147921093 0 4612 0 38136 18446744073709551615 0 0 17 1 0 0 0 0 0 4152360256 4152360952 4157476864 4289182806 4289182882 4289182882 4289183712 0"); + receiver.Flush(); + + Assert.Equal(3, receiver.Processes.Count); + Assert.Equal("/init", receiver.Processes[0].Name); + Assert.Equal("rcu_sched", receiver.Processes[1].Name); + Assert.Equal("be.xx.yy.android.test", receiver.Processes[2].Name); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs index 4fe98904..630ee6f4 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Receivers/VersionInfoReceiverTests.cs @@ -4,12 +4,11 @@ // //----------------------------------------------------------------------- -using AdvancedSharpAdbClient.DeviceCommands; using System; using System.IO; using Xunit; -namespace AdvancedSharpAdbClient.Tests.DeviceCommands +namespace AdvancedSharpAdbClient.DeviceCommands.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs b/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs new file mode 100644 index 00000000..9ad46abf --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.Async.cs @@ -0,0 +1,274 @@ +using System.Linq; +using System.Threading; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class DeviceMonitorTests + { + [Fact] + public async void DeviceDisconnectedAsyncTest() + { + Socket.WaitForNewData = true; + + using DeviceMonitor monitor = new(Socket); + DeviceMonitorSink sink = new(monitor); + + Assert.Equal(0, monitor.Devices.Count); + + // Start the monitor, detect the initial device. + await RunTestAsync( + OkResponse, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests("host:track-devices"), + async () => + { + await monitor.StartAsync(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); + + Socket.ResponseMessages.Clear(); + Socket.Responses.Clear(); + Socket.Requests.Clear(); + + // Device disconnects + ManualResetEvent eventWaiter = sink.CreateEventSignal(); + + RunTest( + NoResponses, + ResponseMessages(""), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(0, monitor.Devices.Count); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Single(sink.DisconnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.DisconnectedEvents[0].Device.Serial); + }); + } + + [Fact] + public async void DeviceConnectedAsyncTest() + { + Socket.WaitForNewData = true; + + using DeviceMonitor monitor = new(Socket); + DeviceMonitorSink sink = new(monitor); + + Assert.Equal(0, monitor.Devices.Count); + + // Start the monitor, detect the initial device. + await RunTestAsync( + OkResponse, + ResponseMessages(""), + Requests("host:track-devices"), + async () => + { + await monitor.StartAsync(); + + Assert.Equal(0, monitor.Devices.Count); + Assert.Empty(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Empty(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); + + Socket.ResponseMessages.Clear(); + Socket.Responses.Clear(); + Socket.Requests.Clear(); + + // Device disconnects + ManualResetEvent eventWaiter = sink.CreateEventSignal(); + + RunTest( + NoResponses, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.ConnectedEvents[0].Device.Serial); + }); + } + + [Fact] + public async void StartInitialDeviceListAsyncTest() + { + Socket.WaitForNewData = true; + + using DeviceMonitor monitor = new(Socket); + DeviceMonitorSink sink = new(monitor); + + Assert.Equal(0, monitor.Devices.Count); + + await RunTestAsync( + OkResponse, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests("host:track-devices"), + async () => + { + await monitor.StartAsync(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal("169.254.109.177:5555", monitor.Devices.ElementAt(0).Serial); + Assert.Single(sink.ConnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.ConnectedEvents[0].Device.Serial); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); + } + + [Fact] + public async void DeviceChanged_TriggeredWhenStatusChangedAsyncTest() + { + Socket.WaitForNewData = true; + + using DeviceMonitor monitor = new(Socket); + DeviceMonitorSink sink = new(monitor); + + Assert.Equal(0, monitor.Devices.Count); + + // Start the monitor, detect the initial device. + await RunTestAsync( + OkResponse, + ResponseMessages("169.254.109.177:5555\toffline\n"), + Requests("host:track-devices"), + async () => + { + await monitor.StartAsync(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); + + Socket.ResponseMessages.Clear(); + Socket.Responses.Clear(); + Socket.Requests.Clear(); + + sink.ResetSignals(); + + // Device disconnects + ManualResetEvent eventWaiter = sink.CreateEventSignal(); + + RunTest( + NoResponses, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Online, monitor.Devices.ElementAt(0).State); + Assert.Empty(sink.ConnectedEvents); + Assert.Single(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.ChangedEvents[0].Device.Serial); + }); + } + + [Fact] + public async void DeviceChanged_NoTriggerIfStatusIsSameAsyncTest() + { + Socket.WaitForNewData = true; + + using DeviceMonitor monitor = new(Socket); + DeviceMonitorSink sink = new(monitor); + + Assert.Equal(0, monitor.Devices.Count); + + // Start the monitor, detect the initial device. + await RunTestAsync( + OkResponse, + ResponseMessages("169.254.109.177:5555\toffline\n"), + Requests("host:track-devices"), + async () => + { + await monitor.StartAsync(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); + + Socket.ResponseMessages.Clear(); + Socket.Responses.Clear(); + Socket.Requests.Clear(); + + sink.ResetSignals(); + + // Something happens but device does not change + ManualResetEvent eventWaiter = sink.CreateEventSignal(); + + RunTest( + NoResponses, + ResponseMessages("169.254.109.177:5555\toffline\n"), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); + Assert.Empty(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); + } + + /// + /// Tests the in a case where the adb server dies in the middle of the monitor + /// loop. The should detect this condition and restart the adb server. + /// + [Fact] + public async void AdbKilledAsyncTest() + { + DummyAdbServer dummyAdbServer = new(); + AdbServer.Instance = dummyAdbServer; + + Socket.WaitForNewData = true; + + using DeviceMonitor monitor = new(Socket); + await RunTestAsync( + new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, + ResponseMessages( + DummyAdbSocket.ServerDisconnected, + string.Empty), + Requests( + "host:track-devices", + "host:track-devices"), + async () => + { + await monitor.StartAsync(); + + Assert.True(Socket.DidReconnect); + Assert.True(dummyAdbServer.WasRestarted); + }); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.cs b/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.cs index 36a6663e..5dc6f271 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceMonitorTests.cs @@ -8,7 +8,7 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class DeviceMonitorTests : SocketBasedTests + public partial class DeviceMonitorTests : SocketBasedTests { // Toggle the integration test flag to true to run on an actual adb server // (and to build/validate the test cases), set to false to use the mocked @@ -43,18 +43,19 @@ public void DeviceDisconnectedTest() // Start the monitor, detect the initial device. RunTest( - OkResponse, - ResponseMessages("169.254.109.177:5555\tdevice\n"), - Requests("host:track-devices"), - () => - { - monitor.Start(); - - Assert.Equal(1, monitor.Devices.Count); - Assert.Single(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - }); + OkResponse, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests("host:track-devices"), + () => + { + monitor.Start(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); Socket.ResponseMessages.Clear(); Socket.Responses.Clear(); @@ -64,18 +65,20 @@ public void DeviceDisconnectedTest() ManualResetEvent eventWaiter = sink.CreateEventSignal(); RunTest( - NoResponses, - ResponseMessages(""), - Requests(), - () => - { - eventWaiter.WaitOne(1000); - Assert.Equal(0, monitor.Devices.Count); - Assert.Single(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Single(sink.DisconnectedEvents); - Assert.Equal("169.254.109.177:5555", sink.DisconnectedEvents[0].Device.Serial); - }); + NoResponses, + ResponseMessages(""), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(0, monitor.Devices.Count); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Single(sink.DisconnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.DisconnectedEvents[0].Device.Serial); + }); } [Fact] @@ -90,18 +93,19 @@ public void DeviceConnectedTest() // Start the monitor, detect the initial device. RunTest( - OkResponse, - ResponseMessages(""), - Requests("host:track-devices"), - () => - { - monitor.Start(); - - Assert.Equal(0, monitor.Devices.Count); - Assert.Empty(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - }); + OkResponse, + ResponseMessages(""), + Requests("host:track-devices"), + () => + { + monitor.Start(); + + Assert.Equal(0, monitor.Devices.Count); + Assert.Empty(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Empty(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); Socket.ResponseMessages.Clear(); Socket.Responses.Clear(); @@ -111,34 +115,33 @@ public void DeviceConnectedTest() ManualResetEvent eventWaiter = sink.CreateEventSignal(); RunTest( - NoResponses, - ResponseMessages("169.254.109.177:5555\tdevice\n"), - Requests(), - () => - { - eventWaiter.WaitOne(1000); - - Assert.Equal(1, monitor.Devices.Count); - Assert.Single(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - Assert.Equal("169.254.109.177:5555", sink.ConnectedEvents[0].Device.Serial); - }); + NoResponses, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.ConnectedEvents[0].Device.Serial); + }); } [Fact] public void StartInitialDeviceListTest() { - lock (FactoriesTests.locker) - { - Socket.WaitForNewData = true; + Socket.WaitForNewData = true; - using DeviceMonitor monitor = new(Socket); - DeviceMonitorSink sink = new(monitor); + using DeviceMonitor monitor = new(Socket); + DeviceMonitorSink sink = new(monitor); - Assert.Equal(0, monitor.Devices.Count); + Assert.Equal(0, monitor.Devices.Count); - RunTest( + RunTest( OkResponse, ResponseMessages("169.254.109.177:5555\tdevice\n"), Requests("host:track-devices"), @@ -151,13 +154,13 @@ public void StartInitialDeviceListTest() Assert.Single(sink.ConnectedEvents); Assert.Equal("169.254.109.177:5555", sink.ConnectedEvents[0].Device.Serial); Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); Assert.Empty(sink.DisconnectedEvents); }); - } } [Fact] - public void DeviceChanged_TriggeredWhenStatusChanged() + public void DeviceChanged_TriggeredWhenStatusChangedTest() { Socket.WaitForNewData = true; @@ -168,19 +171,20 @@ public void DeviceChanged_TriggeredWhenStatusChanged() // Start the monitor, detect the initial device. RunTest( - OkResponse, - ResponseMessages("169.254.109.177:5555\toffline\n"), - Requests("host:track-devices"), - () => - { - monitor.Start(); - - Assert.Equal(1, monitor.Devices.Count); - Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); - Assert.Single(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - }); + OkResponse, + ResponseMessages("169.254.109.177:5555\toffline\n"), + Requests("host:track-devices"), + () => + { + monitor.Start(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); Socket.ResponseMessages.Clear(); Socket.Responses.Clear(); @@ -192,24 +196,25 @@ public void DeviceChanged_TriggeredWhenStatusChanged() ManualResetEvent eventWaiter = sink.CreateEventSignal(); RunTest( - NoResponses, - ResponseMessages("169.254.109.177:5555\tdevice\n"), - Requests(), - () => - { - eventWaiter.WaitOne(1000); - - Assert.Equal(1, monitor.Devices.Count); - Assert.Equal(DeviceState.Online, monitor.Devices.ElementAt(0).State); - Assert.Empty(sink.ConnectedEvents); - Assert.Single(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - Assert.Equal("169.254.109.177:5555", sink.ChangedEvents[0].Device.Serial); - }); + NoResponses, + ResponseMessages("169.254.109.177:5555\tdevice\n"), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Online, monitor.Devices.ElementAt(0).State); + Assert.Empty(sink.ConnectedEvents); + Assert.Single(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + Assert.Equal("169.254.109.177:5555", sink.ChangedEvents[0].Device.Serial); + }); } [Fact] - public void DeviceChanged_NoTriggerIfStatusIsSame() + public void DeviceChanged_NoTriggerIfStatusIsSameTest() { Socket.WaitForNewData = true; @@ -220,19 +225,20 @@ public void DeviceChanged_NoTriggerIfStatusIsSame() // Start the monitor, detect the initial device. RunTest( - OkResponse, - ResponseMessages("169.254.109.177:5555\toffline\n"), - Requests("host:track-devices"), - () => - { - monitor.Start(); - - Assert.Equal(1, monitor.Devices.Count); - Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); - Assert.Single(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - }); + OkResponse, + ResponseMessages("169.254.109.177:5555\toffline\n"), + Requests("host:track-devices"), + () => + { + monitor.Start(); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); + Assert.Single(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); Socket.ResponseMessages.Clear(); Socket.Responses.Clear(); @@ -244,19 +250,20 @@ public void DeviceChanged_NoTriggerIfStatusIsSame() ManualResetEvent eventWaiter = sink.CreateEventSignal(); RunTest( - NoResponses, - ResponseMessages("169.254.109.177:5555\toffline\n"), - Requests(), - () => - { - eventWaiter.WaitOne(1000); - - Assert.Equal(1, monitor.Devices.Count); - Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); - Assert.Empty(sink.ConnectedEvents); - Assert.Empty(sink.ChangedEvents); - Assert.Empty(sink.DisconnectedEvents); - }); + NoResponses, + ResponseMessages("169.254.109.177:5555\toffline\n"), + Requests(), + () => + { + eventWaiter.WaitOne(1000); + + Assert.Equal(1, monitor.Devices.Count); + Assert.Equal(DeviceState.Offline, monitor.Devices.ElementAt(0).State); + Assert.Empty(sink.ConnectedEvents); + Assert.Empty(sink.ChangedEvents); + Assert.Single(sink.NotifiedEvents); + Assert.Empty(sink.DisconnectedEvents); + }); } /// @@ -273,20 +280,20 @@ public void AdbKilledTest() using DeviceMonitor monitor = new(Socket); RunTest( - new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, - ResponseMessages( - DummyAdbSocket.ServerDisconnected, - string.Empty), - Requests( - "host:track-devices", - "host:track-devices"), - () => - { - monitor.Start(); - - Assert.True(Socket.DidReconnect); - Assert.True(dummyAdbServer.WasRestarted); - }); + new AdbResponse[] { AdbResponse.OK, AdbResponse.OK }, + ResponseMessages( + DummyAdbSocket.ServerDisconnected, + string.Empty), + Requests( + "host:track-devices", + "host:track-devices"), + () => + { + monitor.Start(); + + Assert.True(Socket.DidReconnect); + Assert.True(dummyAdbServer.WasRestarted); + }); } } } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DeviceMonitorSink.cs b/AdvancedSharpAdbClient.Tests/Dummys/DeviceMonitorSink.cs index fa59de2c..884e7e3c 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DeviceMonitorSink.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DeviceMonitorSink.cs @@ -10,42 +10,48 @@ public DeviceMonitorSink(DeviceMonitor monitor) { Monitor = monitor ?? throw new ArgumentNullException(nameof(monitor)); Monitor.DeviceChanged += OnDeviceChanged; + Monitor.DeviceNotified += OnDeviceNotified; Monitor.DeviceConnected += OnDeviceConnected; Monitor.DeviceDisconnected += OnDeviceDisconnected; - ChangedEvents = new Collection(); - DisconnectedEvents = new Collection(); - ConnectedEvents = new Collection(); + ChangedEvents = new Collection(); + NotifiedEvents = new Collection(); + ConnectedEvents = new Collection(); + DisconnectedEvents = new Collection(); } public void ResetSignals() { ChangedEvents.Clear(); - DisconnectedEvents.Clear(); + NotifiedEvents.Clear(); ConnectedEvents.Clear(); + DisconnectedEvents.Clear(); } - public Collection DisconnectedEvents { get; private set; } + public Collection DisconnectedEvents { get; private set; } - public Collection ConnectedEvents { get; private set; } + public Collection ConnectedEvents { get; private set; } - public Collection ChangedEvents { get; private set; } + public Collection NotifiedEvents { get; private set; } + + public Collection ChangedEvents { get; private set; } public DeviceMonitor Monitor { get; private set; } public ManualResetEvent CreateEventSignal() { ManualResetEvent signal = new(false); - Monitor.DeviceChanged += (sender, e) => signal.Set(); - Monitor.DeviceConnected += (sender, e) => signal.Set(); + Monitor.DeviceNotified += (sender, e) => signal.Set(); Monitor.DeviceDisconnected += (sender, e) => signal.Set(); return signal; } - protected virtual void OnDeviceDisconnected(object sender, DeviceDataEventArgs e) => DisconnectedEvents.Add(e); + protected virtual void OnDeviceDisconnected(object sender, DeviceDataConnectEventArgs e) => DisconnectedEvents.Add(e); + + protected virtual void OnDeviceConnected(object sender, DeviceDataConnectEventArgs e) => ConnectedEvents.Add(e); - protected virtual void OnDeviceConnected(object sender, DeviceDataEventArgs e) => ConnectedEvents.Add(e); + protected virtual void OnDeviceNotified(object sender, DeviceDataNotifyEventArgs e) => NotifiedEvents.Add(e); - protected virtual void OnDeviceChanged(object sender, DeviceDataEventArgs e) => ChangedEvents.Add(e); + protected virtual void OnDeviceChanged(object sender, DeviceDataChangeEventArgs e) => ChangedEvents.Add(e); } } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs index ca3470be..0feec6a8 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs @@ -123,13 +123,15 @@ public Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellO public Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); + public IAsyncEnumerable FindAsyncElements(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = default) => throw new NotImplementedException(); public Task FindElementAsync(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); - public Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout = default) => throw new NotImplementedException(); + public IEnumerable FindElements(DeviceData device, string xpath, TimeSpan timeout = default) => throw new NotImplementedException(); - public Task FindElementsAsync(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task> FindElementsAsync(DeviceData device, string xpath, CancellationToken cancellationToken) => throw new NotImplementedException(); public int GetAdbVersion() => throw new NotImplementedException(); @@ -235,6 +237,10 @@ public Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellO public Task RootAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); + public void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames) => throw new NotImplementedException(); + + public Task RunLogServiceAsync(DeviceData device, Action messageSink, params LogId[] logNames) => throw new NotImplementedException(); + public Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) => throw new NotImplementedException(); public void SendKeyEvent(DeviceData device, string key) => throw new NotImplementedException(); @@ -247,11 +253,11 @@ public Task ExecuteRemoteCommandAsync(string command, DeviceData device, IShellO public void StartApp(DeviceData device, string packageName) => throw new NotImplementedException(); - public Task StartAppAsync(DeviceData device, string packagename, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task StartAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); public void StopApp(DeviceData device, string packageName) => throw new NotImplementedException(); - public Task StopAppAsync(DeviceData device, string packagename, CancellationToken cancellationToken) => throw new NotImplementedException(); + public Task StopAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken) => throw new NotImplementedException(); public void Swipe(DeviceData device, Element first, Element second, long speed) => throw new NotImplementedException(); diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs index e575bd54..ff2b269f 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbCommandLineClient.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; namespace AdvancedSharpAdbClient.Tests { @@ -46,6 +48,14 @@ protected override int RunAdbProcessInner(string command, List errorOutp return 0; } + protected override Task RunAdbProcessInnerAsync(string command, List errorOutput, List standardOutput, CancellationToken cancellationToken = default) + { + int result = RunAdbProcessInner(command, errorOutput, standardOutput); + TaskCompletionSource tcs = new(); + tcs.SetResult(result); + return tcs.Task; + } + private static string ServerName => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "adb.exe" : "adb"; } } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbServer.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbServer.cs index fdb473af..3be2783a 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbServer.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbServer.cs @@ -1,5 +1,7 @@ using System; using System.Net; +using System.Threading; +using System.Threading.Tasks; namespace AdvancedSharpAdbClient.Tests { @@ -29,7 +31,29 @@ internal class DummyAdbServer : IAdbServer public AdbServerStatus GetStatus() => Status; /// - public void RestartServer() => WasRestarted = true; + public Task GetStatusAsync(CancellationToken cancellationToken = default) + { + TaskCompletionSource tcs = new(); + tcs.SetResult(Status); + return tcs.Task; + } + + /// + public StartServerResult RestartServer(string adbPath = null) + { + WasRestarted = true; + return StartServer(adbPath, false); + } + + /// + public Task RestartServerAsync(CancellationToken cancellationToken = default) => RestartServerAsync(null, cancellationToken); + + /// + public Task RestartServerAsync(string adbPath, CancellationToken cancellationToken = default) + { + WasRestarted = true; + return StartServerAsync(adbPath, false, cancellationToken); + } /// public StartServerResult StartServer(string adbPath, bool restartServerIfNewer) @@ -38,14 +62,17 @@ public StartServerResult StartServer(string adbPath, bool restartServerIfNewer) { return StartServerResult.AlreadyRunning; } - - Status = new AdbServerStatus() - { - IsRunning = true, - Version = new Version(1, 0, 20) - }; - + Status = new AdbServerStatus(true, new Version(1, 0, 20)); return StartServerResult.Started; } + + /// + public Task StartServerAsync(string adbPath, bool restartServerIfNewer, CancellationToken cancellationToken = default) + { + StartServerResult result = StartServer(adbPath, restartServerIfNewer); + TaskCompletionSource tcs = new(); + tcs.SetResult(result); + return tcs.Task; + } } } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs index 03e00bfe..a2f8d6fc 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbSocket.cs @@ -48,69 +48,41 @@ internal class DummyAdbSocket : IAdbSocket, IDummyAdbSocket public Socket Socket => throw new NotImplementedException(); - public void Dispose() => IsConnected = false; + public void Send(byte[] data, int length) => SyncDataSent.Enqueue(data.Take(length).ToArray()); - public int Read(byte[] data) + public void Send(byte[] data, int offset, int length) { - byte[] actual = SyncDataReceived.Dequeue(); - - for (int i = 0; i < data.Length && i < actual.Length; i++) + if (offset == 0) { - data[i] = actual[i]; + Send(data, length); + } + else + { + throw new NotImplementedException(); } - - return actual.Length; } - public Task ReadAsync(byte[] data, CancellationToken cancellationToken = default) - { - Read(data); - - return Task.FromResult(true); - } + public void SendSyncRequest(string command, int value) => SyncRequests.Add((Enum.Parse(command), value.ToString())); - public AdbResponse ReadAdbResponse() - { - AdbResponse response = Responses.Dequeue(); + public void SendSyncRequest(SyncCommand command, string path) => SyncRequests.Add((command, path)); - return !response.Okay ? throw new AdbException(response.Message, response) : response; - } + public void SendSyncRequest(SyncCommand command, int length) => SyncRequests.Add((command, length.ToString())); - public string ReadString() => ReadStringAsync(CancellationToken.None).Result; + public void SendSyncRequest(SyncCommand command, string path, int permissions) => SyncRequests.Add((command, $"{path},{permissions}")); - public string ReadSyncString() => ResponseMessages.Dequeue(); + public void SendAdbRequest(string request) => Requests.Add(request); - public async Task ReadStringAsync(CancellationToken cancellationToken = default) + public int Read(byte[] data) { - if (WaitForNewData) - { - while (ResponseMessages.Count == 0) - { - await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); - cancellationToken.ThrowIfCancellationRequested(); - } - } - - string message = ResponseMessages.Dequeue(); + byte[] actual = SyncDataReceived.Dequeue(); - if (message == ServerDisconnected) - { - SocketException socketException = new(AdbServer.ConnectionReset); - throw new AdbException(socketException.Message, socketException); - } - else + for (int i = 0; i < data.Length && i < actual.Length; i++) { - return message; + data[i] = actual[i]; } - } - - public void SendAdbRequest(string request) => Requests.Add(request); - - public void Close() => IsConnected = false; - - public void SendSyncRequest(string command, int value) => throw new NotImplementedException(); - public void Send(byte[] data, int length) => SyncDataSent.Enqueue(data.Take(length).ToArray()); + return actual.Length; + } public int Read(byte[] data, int length) { @@ -123,13 +95,16 @@ public int Read(byte[] data, int length) return actual.Length; } - public void SendSyncRequest(SyncCommand command, string path) => SyncRequests.Add((command, path)); + public string ReadString() => ReadStringAsync(CancellationToken.None).Result; - public SyncCommand ReadSyncResponse() => SyncResponses.Dequeue(); + public string ReadSyncString() => ResponseMessages.Dequeue(); - public void SendSyncRequest(SyncCommand command, int length) => SyncRequests.Add((command, length.ToString())); + public AdbResponse ReadAdbResponse() + { + AdbResponse response = Responses.Dequeue(); - public void SendSyncRequest(SyncCommand command, string path, int permissions) => SyncRequests.Add((command, $"{path},{permissions}")); + return !response.Okay ? throw new AdbException(response.Message, response) : response; + } public Stream GetShellStream() { @@ -144,22 +119,6 @@ public Stream GetShellStream() } } - public void Reconnect() => DidReconnect = true; - - public Task ReadAsync(byte[] data, int length, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public void Send(byte[] data, int offset, int length) - { - if (offset == 0) - { - Send(data, length); - } - else - { - throw new NotImplementedException(); - } - } - public void SetDevice(DeviceData device) { // if the device is not null, then we first tell adb we're looking to talk @@ -186,6 +145,8 @@ public void SetDevice(DeviceData device) } } + public SyncCommand ReadSyncResponse() => SyncResponses.Dequeue(); + public Task SendAsync(byte[] data, int length, CancellationToken cancellationToken = default) { Send(data, length); @@ -198,19 +159,19 @@ public Task SendAsync(byte[] data, int offset, int length, CancellationToken can return Task.CompletedTask; } - public Task SendSyncRequestAsync(SyncCommand command, string path, int permissions, CancellationToken cancellationToken) + public Task SendSyncRequestAsync(SyncCommand command, string path, int permissions, CancellationToken cancellationToken = default) { SendSyncRequest(command, path, permissions); return Task.CompletedTask; } - public Task SendSyncRequestAsync(SyncCommand command, string path, CancellationToken cancellationToken) + public Task SendSyncRequestAsync(SyncCommand command, string path, CancellationToken cancellationToken = default) { SendSyncRequest(command, path); return Task.CompletedTask; } - public Task SendSyncRequestAsync(SyncCommand command, int length, CancellationToken cancellationToken) + public Task SendSyncRequestAsync(SyncCommand command, int length, CancellationToken cancellationToken = default) { SendSyncRequest(command, length); return Task.CompletedTask; @@ -222,34 +183,80 @@ public Task SendAdbRequestAsync(string request, CancellationToken cancellationTo return Task.CompletedTask; } - public Task ReadSyncStringAsync(CancellationToken cancellationToken) + public Task ReadAsync(byte[] data, CancellationToken cancellationToken = default) + { + int result = Read(data); + TaskCompletionSource tcs = new(); + tcs.SetResult(result); + return tcs.Task; + } + + public Task ReadAsync(byte[] data, int length, CancellationToken cancellationToken = default) { - var response = ReadSyncString(); - var tcs = new TaskCompletionSource(); + int result = Read(data, length); + TaskCompletionSource tcs = new(); + tcs.SetResult(result); + return tcs.Task; + } + + public async Task ReadStringAsync(CancellationToken cancellationToken = default) + { + if (WaitForNewData) + { + while (ResponseMessages.Count == 0) + { + await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + } + } + + string message = ResponseMessages.Dequeue(); + + if (message == ServerDisconnected) + { + SocketException socketException = new(AdbServer.ConnectionReset); + throw new AdbException(socketException.Message, socketException); + } + else + { + return message; + } + } + + public Task ReadSyncStringAsync(CancellationToken cancellationToken = default) + { + string response = ReadSyncString(); + TaskCompletionSource tcs = new(); tcs.SetResult(response); return tcs.Task; } - public Task ReadSyncResponseAsync(CancellationToken cancellationToken) + public Task ReadSyncResponseAsync(CancellationToken cancellationToken = default) { - var response = ReadSyncResponse(); - var tcs = new TaskCompletionSource(); + SyncCommand response = ReadSyncResponse(); + TaskCompletionSource tcs = new(); tcs.SetResult(response); return tcs.Task; } public Task ReadAdbResponseAsync(CancellationToken cancellationToken = default) { - var response = ReadAdbResponse(); - var tcs = new TaskCompletionSource(); + AdbResponse response = ReadAdbResponse(); + TaskCompletionSource tcs = new(); tcs.SetResult(response); return tcs.Task; } - public Task SetDeviceAsync(DeviceData device, CancellationToken cancellationToken) + public Task SetDeviceAsync(DeviceData device, CancellationToken cancellationToken = default) { SetDevice(device); return Task.CompletedTask; } + + public void Dispose() => IsConnected = false; + + public void Close() => IsConnected = false; + + public void Reconnect() => DidReconnect = true; } } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs index b7e78880..fcbffcb4 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs @@ -30,26 +30,26 @@ public void Open() public Task OpenAsync(CancellationToken cancellationToken) => Task.CompletedTask; - public void Pull(string remotePath, Stream stream, IProgress progress, CancellationToken cancellationToken) => + public void Pull(string remotePath, Stream stream, IProgress progress, CancellationToken cancellationToken = default) => SyncProgressChanged?.Invoke(this, new SyncProgressChangedEventArgs(100, 100)); - public Task PullAsync(string remotePath, Stream stream, IProgress progress, CancellationToken cancellationToken) + public Task PullAsync(string remotePath, Stream stream, IProgress progress, CancellationToken cancellationToken = default) { SyncProgressChanged?.Invoke(this, new SyncProgressChangedEventArgs(100, 100)); return Task.CompletedTask; } - public void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress progress, CancellationToken cancellationToken) + public void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress progress, CancellationToken cancellationToken = default) { SyncProgressChanged?.Invoke(this, new SyncProgressChangedEventArgs(0, 100)); - UploadedFiles.Add(remotePath, stream); + UploadedFiles[remotePath] = stream; SyncProgressChanged?.Invoke(this, new SyncProgressChangedEventArgs(100, 100)); } - public Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress progress, CancellationToken cancellationToken) + public Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress progress, CancellationToken cancellationToken = default) { SyncProgressChanged?.Invoke(this, new SyncProgressChangedEventArgs(0, 100)); - UploadedFiles.Add(remotePath, stream); + UploadedFiles[remotePath] = stream; SyncProgressChanged?.Invoke(this, new SyncProgressChangedEventArgs(100, 100)); return Task.CompletedTask; } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyTcpSocket.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyTcpSocket.cs index 94678fff..6d388205 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyTcpSocket.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyTcpSocket.cs @@ -38,8 +38,8 @@ public Task ConnectAsync(EndPoint endPoint) public Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) { - var result = Send(buffer, offset, size, socketFlags); - var tcs = new TaskCompletionSource(); + int result = Send(buffer, offset, size, socketFlags); + TaskCompletionSource tcs = new(); tcs.SetResult(result); return tcs.Task; } diff --git a/AdvancedSharpAdbClient.Tests/Dummys/IDummyAdbSocket.cs b/AdvancedSharpAdbClient.Tests/Dummys/IDummyAdbSocket.cs index b0257854..94dc2da0 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/IDummyAdbSocket.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/IDummyAdbSocket.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; namespace AdvancedSharpAdbClient.Tests diff --git a/AdvancedSharpAdbClient.Tests/Dummys/TracingAdbSocket.cs b/AdvancedSharpAdbClient.Tests/Dummys/TracingAdbSocket.cs index de6ba87a..bb3d5c3e 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/TracingAdbSocket.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/TracingAdbSocket.cs @@ -48,11 +48,7 @@ public override void Dispose() public override int Read(byte[] data) { -#if NETCOREAPP1_1 - StackTrace trace = null; -#else StackTrace trace = new(); -#endif int read = base.Read(data); @@ -66,11 +62,7 @@ public override int Read(byte[] data) public override int Read(byte[] data, int length) { -#if NETCOREAPP1_1 - StackTrace trace = null; -#else StackTrace trace = new(); -#endif int read = base.Read(data, length); @@ -137,11 +129,7 @@ public override void SendSyncRequest(SyncCommand command, string path) public override void SendSyncRequest(SyncCommand command, int length) { -#if NETCOREAPP1_1 - StackTrace trace = null; -#else StackTrace trace = new(); -#endif if (trace != null && trace.GetFrames()[1].GetMethod().DeclaringType != typeof(AdbSocket)) { @@ -160,11 +148,7 @@ public override SyncCommand ReadSyncResponse() public override void Send(byte[] data, int length) { -#if NETCOREAPP1_1 - StackTrace trace = null; -#else StackTrace trace = new(); -#endif base.Send(data, length); diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/AdbExceptionTests.cs b/AdvancedSharpAdbClient.Tests/Exceptions/AdbExceptionTests.cs index 07964624..8c17e5d8 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/AdbExceptionTests.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/AdbExceptionTests.cs @@ -1,26 +1,23 @@ -using AdvancedSharpAdbClient.Exceptions; -using Xunit; +using Xunit; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { public class AdbExceptionTests { [Fact] - public void TestEmptyConstructor() => - ExceptionTester.TestEmptyConstructor(() => new AdbException()); + public void EmptyConstructorTest() => + ExceptionTester.EmptyConstructorTest(() => new AdbException()); [Fact] - public void TestMessageConstructor() => - ExceptionTester.TestMessageConstructor((message) => new AdbException(message)); + public void MessageConstructorTest() => + ExceptionTester.MessageConstructorTest((message) => new AdbException(message)); [Fact] - public void TestMessageAndInnerConstructor() => - ExceptionTester.TestMessageAndInnerConstructor((message, inner) => new AdbException(message, inner)); + public void MessageAndInnerConstructorTest() => + ExceptionTester.MessageAndInnerConstructorTest((message, inner) => new AdbException(message, inner)); -#if !NETCOREAPP1_1 [Fact] - public void TestSerializationConstructor() => - ExceptionTester.TestSerializationConstructor((info, context) => new AdbException(info, context)); -#endif + public void SerializationConstructorTest() => + ExceptionTester.SerializationConstructorTest((info, context) => new AdbException(info, context)); } } diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/CommandAbortingExceptionTests.cs b/AdvancedSharpAdbClient.Tests/Exceptions/CommandAbortingExceptionTests.cs index f38e22cc..2732fdbb 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/CommandAbortingExceptionTests.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/CommandAbortingExceptionTests.cs @@ -1,26 +1,23 @@ -using AdvancedSharpAdbClient.Exceptions; -using Xunit; +using Xunit; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { public class CommandAbortingExceptionTests { [Fact] - public void TestEmptyConstructor() => - ExceptionTester.TestEmptyConstructor(() => new CommandAbortingException()); + public void EmptyConstructorTest() => + ExceptionTester.EmptyConstructorTest(() => new CommandAbortingException()); [Fact] - public void TestMessageConstructor() => - ExceptionTester.TestMessageConstructor((message) => new CommandAbortingException(message)); + public void MessageConstructorTest() => + ExceptionTester.MessageConstructorTest((message) => new CommandAbortingException(message)); [Fact] - public void TestMessageAndInnerConstructor() => - ExceptionTester.TestMessageAndInnerConstructor((message, inner) => new CommandAbortingException(message, inner)); + public void MessageAndInnerConstructorTest() => + ExceptionTester.MessageAndInnerConstructorTest((message, inner) => new CommandAbortingException(message, inner)); -#if !NETCOREAPP1_1 [Fact] - public void TestSerializationConstructor() => - ExceptionTester.TestSerializationConstructor((info, context) => new CommandAbortingException(info, context)); -#endif + public void SerializationConstructorTest() => + ExceptionTester.SerializationConstructorTest((info, context) => new CommandAbortingException(info, context)); } } diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/DeviceNotFoundException.cs b/AdvancedSharpAdbClient.Tests/Exceptions/DeviceNotFoundException.cs index 9993de26..7a77ff93 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/DeviceNotFoundException.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/DeviceNotFoundException.cs @@ -1,22 +1,19 @@ -using AdvancedSharpAdbClient.Exceptions; -using Xunit; +using Xunit; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { public class DeviceNotFoundExceptionTests { [Fact] - public void TestEmptyConstructor() => - ExceptionTester.TestEmptyConstructor(() => new DeviceNotFoundException()); + public void EmptyConstructorTest() => + ExceptionTester.EmptyConstructorTest(() => new DeviceNotFoundException()); [Fact] - public void TestMessageAndInnerConstructor() => - ExceptionTester.TestMessageAndInnerConstructor((message, inner) => new DeviceNotFoundException(message, inner)); + public void MessageAndInnerConstructorTest() => + ExceptionTester.MessageAndInnerConstructorTest((message, inner) => new DeviceNotFoundException(message, inner)); -#if !NETCOREAPP1_1 [Fact] - public void TestSerializationConstructor() => - ExceptionTester.TestSerializationConstructor((info, context) => new DeviceNotFoundException(info, context)); -#endif + public void SerializationConstructorTest() => + ExceptionTester.SerializationConstructorTest((info, context) => new DeviceNotFoundException(info, context)); } } diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionExtensionsTests.cs b/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionExtensionsTests.cs index 5df31719..288d02f4 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionExtensionsTests.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionExtensionsTests.cs @@ -1,11 +1,10 @@ using System; using Xunit; -using AdvancedSharpAdbClient.Exceptions; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { /// - /// Tests the class. + /// Tests the class. /// public class ExceptionExtensionsTests { diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionTester.cs b/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionTester.cs index cb6b6649..6d1895b3 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionTester.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/ExceptionTester.cs @@ -2,16 +2,16 @@ using System.Runtime.Serialization; using Xunit; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { /// /// Tests the class. /// internal static class ExceptionTester where T : Exception { - public static void TestEmptyConstructor(Func constructor) => _ = constructor(); + public static void EmptyConstructorTest(Func constructor) => _ = constructor(); - public static void TestMessageConstructor(Func constructor) + public static void MessageConstructorTest(Func constructor) { string message = "Hello, World"; T ex = constructor(message); @@ -20,7 +20,7 @@ public static void TestMessageConstructor(Func constructor) Assert.Null(ex.InnerException); } - public static void TestMessageAndInnerConstructor(Func constructor) + public static void MessageAndInnerConstructorTest(Func constructor) { string message = "Hello, World"; Exception inner = new(); @@ -30,8 +30,7 @@ public static void TestMessageAndInnerConstructor(Func con Assert.Equal(inner, ex.InnerException); } -#if !NETCOREAPP1_1 - public static void TestSerializationConstructor(Func constructor) + public static void SerializationConstructorTest(Func constructor) { SerializationInfo info = new(typeof(T), new FormatterConverter()); StreamingContext context = new(); @@ -49,6 +48,5 @@ public static void TestSerializationConstructor(Func - ExceptionTester.TestEmptyConstructor(() => new PermissionDeniedException()); + public void EmptyConstructorTest() => + ExceptionTester.EmptyConstructorTest(() => new PermissionDeniedException()); [Fact] - public void TestMessageConstructor() => - ExceptionTester.TestMessageConstructor((message) => new PermissionDeniedException(message)); + public void MessageConstructorTest() => + ExceptionTester.MessageConstructorTest((message) => new PermissionDeniedException(message)); [Fact] - public void TestMessageAndInnerConstructor() => - ExceptionTester.TestMessageAndInnerConstructor((message, inner) => new PermissionDeniedException(message, inner)); + public void MessageAndInnerConstructorTest() => + ExceptionTester.MessageAndInnerConstructorTest((message, inner) => new PermissionDeniedException(message, inner)); -#if !NETCOREAPP1_1 [Fact] - public void TestSerializationConstructor() => - ExceptionTester.TestSerializationConstructor((info, context) => new PermissionDeniedException(info, context)); -#endif + public void SerializationConstructorTest() => + ExceptionTester.SerializationConstructorTest((info, context) => new PermissionDeniedException(info, context)); } } diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/ShellCommandUnresponsiveExceptionTests.cs b/AdvancedSharpAdbClient.Tests/Exceptions/ShellCommandUnresponsiveExceptionTests.cs index 5ebc8c4b..7cd38a6c 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/ShellCommandUnresponsiveExceptionTests.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/ShellCommandUnresponsiveExceptionTests.cs @@ -1,26 +1,23 @@ -using AdvancedSharpAdbClient.Exceptions; -using Xunit; +using Xunit; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { public class ShellCommandUnresponsiveExceptionTests { [Fact] - public void TestEmptyConstructor() => - ExceptionTester.TestEmptyConstructor(() => new ShellCommandUnresponsiveException()); + public void EmptyConstructorTest() => + ExceptionTester.EmptyConstructorTest(() => new ShellCommandUnresponsiveException()); [Fact] - public void TestMessageConstructor() => - ExceptionTester.TestMessageConstructor((message) => new ShellCommandUnresponsiveException(message)); + public void MessageConstructorTest() => + ExceptionTester.MessageConstructorTest((message) => new ShellCommandUnresponsiveException(message)); [Fact] - public void TestMessageAndInnerConstructor() => - ExceptionTester.TestMessageAndInnerConstructor((message, inner) => new ShellCommandUnresponsiveException(message, inner)); + public void MessageAndInnerConstructorTest() => + ExceptionTester.MessageAndInnerConstructorTest((message, inner) => new ShellCommandUnresponsiveException(message, inner)); -#if !NETCOREAPP1_1 [Fact] - public void TestSerializationConstructor() => - ExceptionTester.TestSerializationConstructor((info, context) => new ShellCommandUnresponsiveException(info, context)); -#endif + public void SerializationConstructorTest() => + ExceptionTester.SerializationConstructorTest((info, context) => new ShellCommandUnresponsiveException(info, context)); } } diff --git a/AdvancedSharpAdbClient.Tests/Exceptions/UnknownOptionExceptionTests.cs b/AdvancedSharpAdbClient.Tests/Exceptions/UnknownOptionExceptionTests.cs index 8d4eed43..58239de8 100644 --- a/AdvancedSharpAdbClient.Tests/Exceptions/UnknownOptionExceptionTests.cs +++ b/AdvancedSharpAdbClient.Tests/Exceptions/UnknownOptionExceptionTests.cs @@ -1,26 +1,23 @@ -using AdvancedSharpAdbClient.Exceptions; -using Xunit; +using Xunit; -namespace AdvancedSharpAdbClient.Tests.Exceptions +namespace AdvancedSharpAdbClient.Exceptions.Tests { public class UnknownOptionExceptionTests { [Fact] - public void TestEmptyConstructor() => - ExceptionTester.TestEmptyConstructor(() => new UnknownOptionException()); + public void EmptyConstructorTest() => + ExceptionTester.EmptyConstructorTest(() => new UnknownOptionException()); [Fact] - public void TestMessageConstructor() => - ExceptionTester.TestMessageConstructor((message) => new UnknownOptionException(message)); + public void MessageConstructorTest() => + ExceptionTester.MessageConstructorTest((message) => new UnknownOptionException(message)); [Fact] - public void TestMessageAndInnerConstructor() => - ExceptionTester.TestMessageAndInnerConstructor((message, inner) => new UnknownOptionException(message, inner)); + public void MessageAndInnerConstructorTest() => + ExceptionTester.MessageAndInnerConstructorTest((message, inner) => new UnknownOptionException(message, inner)); -#if !NETCOREAPP1_1 [Fact] - public void TestSerializationConstructor() => - ExceptionTester.TestSerializationConstructor((info, context) => new UnknownOptionException(info, context)); -#endif + public void SerializationConstructorTest() => + ExceptionTester.SerializationConstructorTest((info, context) => new UnknownOptionException(info, context)); } } diff --git a/AdvancedSharpAdbClient.Tests/Extensions/DateTimeHelperTests.cs b/AdvancedSharpAdbClient.Tests/Extensions/DateTimeHelperTests.cs index f902a48c..ab4c8843 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/DateTimeHelperTests.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/DateTimeHelperTests.cs @@ -16,7 +16,7 @@ public void ToUnixEpochTest() } [Fact] - public void ToDateTime() + public void ToDateTimeTest() { DateTime time = new(2022, 6, 1, 12, 10, 34, DateTimeKind.Utc); Assert.Equal(time, ((long)1654085434).ToDateTime()); diff --git a/AdvancedSharpAdbClient.Tests/Extensions/UtilitiesTests.cs b/AdvancedSharpAdbClient.Tests/Extensions/UtilitiesTests.cs index 54824607..1af7da56 100644 --- a/AdvancedSharpAdbClient.Tests/Extensions/UtilitiesTests.cs +++ b/AdvancedSharpAdbClient.Tests/Extensions/UtilitiesTests.cs @@ -1,7 +1,7 @@ using System; using Xunit; -namespace AdvancedSharpAdbClient.Tests.Extensions +namespace AdvancedSharpAdbClient.Tests { /// /// Tests the class. diff --git a/AdvancedSharpAdbClient.Tests/Logs/LoggerTests.cs b/AdvancedSharpAdbClient.Tests/Logs/LoggerTests.cs index 1dc64884..cb1c6efa 100644 --- a/AdvancedSharpAdbClient.Tests/Logs/LoggerTests.cs +++ b/AdvancedSharpAdbClient.Tests/Logs/LoggerTests.cs @@ -1,17 +1,15 @@ -using AdvancedSharpAdbClient.Logs; -using System; +using System; using System.Collections.ObjectModel; using System.IO; using System.Threading; -using System.Threading.Tasks; using Xunit; -namespace AdvancedSharpAdbClient.Tests.Logs +namespace AdvancedSharpAdbClient.Logs.Tests { public class LoggerTests { [Fact] - public async Task ReadLogTests() + public void ReadLogTest() { using Stream stream = File.OpenRead(@"Assets/logcat.bin"); using ShellStream shellStream = new(stream, false); @@ -19,7 +17,7 @@ public async Task ReadLogTests() // This stream contains 3 log entries. Read & validate the first one, // read the next two ones (to be sure all data is read correctly). - LogEntry log = await reader.ReadEntry(CancellationToken.None); + LogEntry log = reader.ReadEntry(); Assert.IsType(log); @@ -35,18 +33,81 @@ public async Task ReadLogTests() Assert.Equal("ActivityManager", androidLog.Tag); Assert.Equal("Start proc com.google.android.gm for broadcast com.google.android.gm/.widget.GmailWidgetProvider: pid=7026 uid=10066 gids={50066, 9997, 3003, 1028, 1015} abi=x86", androidLog.Message); - Assert.NotNull(await reader.ReadEntry(CancellationToken.None)); - Assert.NotNull(await reader.ReadEntry(CancellationToken.None)); + Assert.NotNull(reader.ReadEntry()); + Assert.NotNull(reader.ReadEntry()); } [Fact] - public async Task ReadEventLogTest() + public async void ReadLogAsyncTest() + { + using Stream stream = File.OpenRead(@"Assets/logcat.bin"); + using ShellStream shellStream = new(stream, false); + LogReader reader = new(shellStream); + + // This stream contains 3 log entries. Read & validate the first one, + // read the next two ones (to be sure all data is read correctly). + LogEntry log = await reader.ReadEntryAsync(CancellationToken.None); + + Assert.IsType(log); + + Assert.Equal(707, log.ProcessId); + Assert.Equal(1254, log.ThreadId); + Assert.Equal(3u, log.Id); + Assert.NotNull(log.Data); + Assert.Equal(179, log.Data.Length); + Assert.Equal(new DateTime(2015, 11, 14, 23, 38, 20, DateTimeKind.Utc), log.TimeStamp); + + AndroidLogEntry androidLog = (AndroidLogEntry)log; + Assert.Equal(Priority.Info, androidLog.Priority); + Assert.Equal("ActivityManager", androidLog.Tag); + Assert.Equal("Start proc com.google.android.gm for broadcast com.google.android.gm/.widget.GmailWidgetProvider: pid=7026 uid=10066 gids={50066, 9997, 3003, 1028, 1015} abi=x86", androidLog.Message); + + Assert.NotNull(await reader.ReadEntryAsync(CancellationToken.None)); + Assert.NotNull(await reader.ReadEntryAsync(CancellationToken.None)); + } + + [Fact] + public void ReadEventLogTest() + { + // The data in this stream was read using a ShellStream, so the CRLF fixing + // has already taken place. + using Stream stream = File.OpenRead(@"Assets/logcatevents.bin"); + LogReader reader = new(stream); + LogEntry entry = reader.ReadEntry(); + + Assert.IsType(entry); + Assert.Equal(707, entry.ProcessId); + Assert.Equal(1291, entry.ThreadId); + Assert.Equal(2u, entry.Id); + Assert.NotNull(entry.Data); + Assert.Equal(39, entry.Data.Length); + Assert.Equal(new DateTime(2015, 11, 16, 1, 48, 40, DateTimeKind.Utc), entry.TimeStamp); + + EventLogEntry eventLog = (EventLogEntry)entry; + Assert.Equal(0, eventLog.Tag); + Assert.NotNull(eventLog.Values); + Assert.Single(eventLog.Values); + Assert.NotNull(eventLog.Values[0]); + Assert.IsType>(eventLog.Values[0]); + + Collection list = (Collection)eventLog.Values[0]; + Assert.Equal(3, list.Count); + Assert.Equal(0, list[0]); + Assert.Equal(19512, list[1]); + Assert.Equal("com.amazon.kindle", list[2]); + + entry = reader.ReadEntry(); + entry = reader.ReadEntry(); + } + + [Fact] + public async void ReadEventLogAsyncTest() { // The data in this stream was read using a ShellStream, so the CRLF fixing // has already taken place. using Stream stream = File.OpenRead(@"Assets/logcatevents.bin"); LogReader reader = new(stream); - LogEntry entry = await reader.ReadEntry(CancellationToken.None); + LogEntry entry = await reader.ReadEntryAsync(CancellationToken.None); Assert.IsType(entry); Assert.Equal(707, entry.ProcessId); @@ -69,8 +130,8 @@ public async Task ReadEventLogTest() Assert.Equal(19512, list[1]); Assert.Equal("com.amazon.kindle", list[2]); - entry = await reader.ReadEntry(CancellationToken.None); - entry = await reader.ReadEntry(CancellationToken.None); + entry = await reader.ReadEntryAsync(CancellationToken.None); + entry = await reader.ReadEntryAsync(CancellationToken.None); } } } diff --git a/AdvancedSharpAdbClient.Tests/Logs/ShellStreamTests.cs b/AdvancedSharpAdbClient.Tests/Logs/ShellStreamTests.cs index f29bbbc7..fbb74ef9 100644 --- a/AdvancedSharpAdbClient.Tests/Logs/ShellStreamTests.cs +++ b/AdvancedSharpAdbClient.Tests/Logs/ShellStreamTests.cs @@ -1,11 +1,10 @@ -using AdvancedSharpAdbClient.Logs; -using System; +using System; using System.IO; using System.Text; using System.Threading.Tasks; using Xunit; -namespace AdvancedSharpAdbClient.Tests.Logs +namespace AdvancedSharpAdbClient.Logs.Tests { public class ShellStreamTests { @@ -41,7 +40,7 @@ public void ConstructorTest() } [Fact] - public void TestCRLFAtStart() + public void CRLFAtStartTest() { using MemoryStream stream = GetStream("\r\nHello, World!"); using ShellStream shellStream = new(stream, false); @@ -60,7 +59,7 @@ public void TestCRLFAtStart() } [Fact] - public void MultipleCRLFInString() + public void MultipleCRLFInStringTest() { using MemoryStream stream = GetStream("\r\n1\r\n2\r\n3\r\n4\r\n5"); using ShellStream shellStream = new(stream, false); @@ -82,7 +81,7 @@ public void MultipleCRLFInString() } [Fact] - public void PendingByteTest1() + public void PendingByteTest() { using MemoryStream stream = GetStream("\r\nH\ra"); using ShellStream shellStream = new(stream, false); @@ -105,7 +104,7 @@ public void PendingByteTest1() } [Fact] - public async Task TestCRLFAtStartAsync() + public async void CRLFAtStartAsyncTest() { using MemoryStream stream = GetStream("\r\nHello, World!"); using ShellStream shellStream = new(stream, false); @@ -124,7 +123,7 @@ public async Task TestCRLFAtStartAsync() } [Fact] - public async Task MultipleCRLFInStringAsync() + public async void MultipleCRLFInStringAsyncTest() { using MemoryStream stream = GetStream("\r\n1\r\n2\r\n3\r\n4\r\n5"); using ShellStream shellStream = new(stream, false); @@ -146,7 +145,7 @@ public async Task MultipleCRLFInStringAsync() } [Fact] - public async Task PendingByteTest1Async() + public async void PendingByteAsyncTest() { using MemoryStream stream = GetStream("\r\nH\ra"); using ShellStream shellStream = new(stream, false); diff --git a/AdvancedSharpAdbClient.Tests/Models/AdbServerStatusTests.cs b/AdvancedSharpAdbClient.Tests/Models/AdbServerStatusTests.cs index d02ea16b..c6bca265 100644 --- a/AdvancedSharpAdbClient.Tests/Models/AdbServerStatusTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/AdbServerStatusTests.cs @@ -11,16 +11,9 @@ public class AdbServerStatusTests [Fact] public void ToStringTest() { - AdbServerStatus s = new() - { - IsRunning = true, - Version = new Version(1, 0, 32) - }; - + AdbServerStatus s = new(true, new Version(1, 0, 32)); Assert.Equal("Version 1.0.32 of the adb daemon is running.", s.ToString()); - s.IsRunning = false; - Assert.Equal("The adb daemon is not running.", s.ToString()); } } diff --git a/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs b/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs index dc65b62e..6a39a4a0 100644 --- a/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/AreaTests.cs @@ -194,7 +194,7 @@ public void AreaFConversionTest(float x, float y, float width, float height) [InlineData(0, int.MinValue, int.MaxValue, 0)] public void ContainsTest(int x, int y, int width, int height) { - Area rect = new(unchecked(2 * x - width), unchecked(2 * y - height), width, height); + Area rect = new(unchecked((2 * x) - width), unchecked((2 * y) - height), width, height); Cords p = new(x, y); Area r = new(x, y, width / 2, height / 2); @@ -212,7 +212,7 @@ public void InflateTest(int x, int y, int width, int height) Area inflatedRect, rect = new(x, y, width, height); unchecked { - inflatedRect = new Area(x - width, y - height, width + 2 * width, height + 2 * height); + inflatedRect = new Area(x - width, y - height, width + (2 * width), height + (2 * height)); } Assert.Equal(inflatedRect, Area.Inflate(rect, width, height)); @@ -223,7 +223,7 @@ public void InflateTest(int x, int y, int width, int height) Size s = new(x, y); unchecked { - inflatedRect = new Area(rect.X - x, rect.Y - y, rect.Width + 2 * x, rect.Height + 2 * y); + inflatedRect = new Area(rect.X - x, rect.Y - y, rect.Width + (2 * x), rect.Height + (2 * y)); } rect.Inflate(s); diff --git a/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs b/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs index 542a2501..11144573 100644 --- a/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/CordsTests.cs @@ -163,7 +163,7 @@ public void OffsetTest(int x, int y) public void EqualityTest(int x, int y) { Cords p1 = new(x, y); - Cords p2 = new((x / 2) - 1, y / 2 - 1); + Cords p2 = new((x / 2) - 1, (y / 2) - 1); Cords p3 = new(x, y); Assert.True(p1 == p3); diff --git a/AdvancedSharpAdbClient.Tests/Models/DeviceDataTests.cs b/AdvancedSharpAdbClient.Tests/Models/DeviceDataTests.cs index bdbe1b60..6b413da8 100644 --- a/AdvancedSharpAdbClient.Tests/Models/DeviceDataTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/DeviceDataTests.cs @@ -18,12 +18,12 @@ public void CreateFromDeviceDataVSEmulatorTest() Assert.Equal("VS Emulator Android Device - 480 x 800", device.Product); Assert.Equal("Android_Device___480_x_800", device.Model); Assert.Equal("donatello", device.Name); - Assert.Equal(DeviceState.Offline, device.State); + Assert.Equal(DeviceState.Offline, device.State); Assert.Equal(string.Empty, device.Usb); } [Fact] - public void CreateFromDeviceNoPermissionTest2() + public void CreateFromDeviceNoPermissionTest() { string data = "009d1cd696d5194a no permissions (user in plugdev group; are your udev rules wrong?); see [http://developer.android.com/tools/device.html"; @@ -33,7 +33,7 @@ public void CreateFromDeviceNoPermissionTest2() Assert.Equal(string.Empty, device.Model); Assert.Equal(string.Empty, device.Name); Assert.Equal(string.Empty, device.Features); - Assert.Equal(DeviceState.NoPermissions, device.State); + Assert.Equal(DeviceState.NoPermissions, device.State); Assert.Equal(" (user in plugdev group; are your udev rules wrong?); see [http://developer.android.com/tools/device.html", device.Message); Assert.Equal(string.Empty, device.Usb); Assert.Equal(string.Empty, device.TransportId); @@ -50,7 +50,7 @@ public void CreateFromDeviceDataAuthorizingTest() Assert.Equal(string.Empty, device.Model); Assert.Equal(string.Empty, device.Name); Assert.Equal(string.Empty, device.Features); - Assert.Equal(DeviceState.Authorizing, device.State); + Assert.Equal(DeviceState.Authorizing, device.State); Assert.Equal("9-1.4.1", device.Usb); Assert.Equal("8149", device.TransportId); } @@ -65,7 +65,7 @@ public void CreateFromDeviceDataUnauthorizedTest() Assert.Equal("", device.Product); Assert.Equal("", device.Model); Assert.Equal("", device.Name); - Assert.Equal(DeviceState.Unauthorized, device.State); + Assert.Equal(DeviceState.Unauthorized, device.State); Assert.Equal(string.Empty, device.Usb); } @@ -76,7 +76,7 @@ public void CreateFromEmulatorTest() DeviceData device = DeviceData.CreateFromAdbData(data); Assert.Equal("emulator-5586", device.Serial); - Assert.Equal(DeviceState.Host, device.State); + Assert.Equal(DeviceState.Host, device.State); Assert.Equal("shell_2", device.Features); Assert.Equal(string.Empty, device.Usb); } @@ -88,7 +88,7 @@ public void CreateWithFeaturesTest() DeviceData device = DeviceData.CreateFromAdbData(data); Assert.Equal("0100a9ee51a18f2b", device.Serial); - Assert.Equal(DeviceState.Online, device.State); + Assert.Equal(DeviceState.Online, device.State); Assert.Equal("Nexus_5X", device.Model); Assert.Equal("bullhead", device.Product); Assert.Equal("bullhead", device.Name); @@ -104,7 +104,7 @@ public void CreateWithUsbDataTest() DeviceData device = DeviceData.CreateFromAdbData(data); Assert.Equal("EAOKCY112414", device.Serial); - Assert.Equal(DeviceState.Online, device.State); + Assert.Equal(DeviceState.Online, device.State); Assert.Equal("K013", device.Model); Assert.Equal("WW_K013", device.Product); Assert.Equal("K013_1", device.Name); @@ -120,7 +120,7 @@ public void CreateWithoutModelTest() DeviceData device = DeviceData.CreateFromAdbData(data); Assert.Equal("ZY3222LBDC", device.Serial); - Assert.Equal(DeviceState.Recovery, device.State); + Assert.Equal(DeviceState.Recovery, device.State); Assert.Equal("337641472X", device.Usb); Assert.Equal(string.Empty, device.Model); Assert.Equal("omni_cedric", device.Product); @@ -150,7 +150,7 @@ public void CreateWithUnderscoresTest() } [Fact] - public void CreateFromInvalidDatatest() + public void CreateFromInvalidDataTest() { string data = "xyz"; @@ -175,52 +175,24 @@ public void GetStateFromStringTest() Assert.Equal(DeviceState.Unknown, DeviceData.GetStateFromString("hello")); } - [Fact] - public void CreateFromDeviceDataTransportIdTest() - { - string data = "R32D102SZAE device transport_id:6"; - - DeviceData device = DeviceData.CreateFromAdbData(data); - Assert.Equal("R32D102SZAE", device.Serial); - Assert.Equal("", device.Product); - Assert.Equal("", device.Model); - Assert.Equal("", device.Name); - Assert.Equal(DeviceState.Online, device.State); - Assert.Equal(string.Empty, device.Usb); - } - - [Fact] - public void CreateFromDeviceDataTransportIdTest2() - { - string data = "emulator-5554 device product:sdk_google_phone_x86 model:Android_SDK_built_for_x86 device:generic_x86 transport_id:1"; - - DeviceData device = DeviceData.CreateFromAdbData(data); - Assert.Equal("emulator-5554", device.Serial); - Assert.Equal("sdk_google_phone_x86", device.Product); - Assert.Equal("Android_SDK_built_for_x86", device.Model); - Assert.Equal("generic_x86", device.Name); - Assert.Equal("1", device.TransportId); - Assert.Equal(DeviceState.Online, device.State); - Assert.Equal(string.Empty, device.Usb); - } - - [Fact] - public void CreateFromDeviceDataTransportIdTest3() + [Theory] + [InlineData("R32D102SZAE device transport_id:6", "R32D102SZAE", "", "", "", "6")] + [InlineData("emulator-5554 device product:sdk_google_phone_x86 model:Android_SDK_built_for_x86 device:generic_x86 transport_id:1", "emulator-5554", "sdk_google_phone_x86", "Android_SDK_built_for_x86", "generic_x86", "1")] + [InlineData("00bc13bcf4bacc62 device product:bullhead model:Nexus_5X device:bullhead transport_id:1", "00bc13bcf4bacc62", "bullhead", "Nexus_5X", "bullhead", "1")] + public void CreateFromDeviceDataTransportIdTest(string data, string serial, string product, string model, string name, string transportId) { - string data = "00bc13bcf4bacc62 device product:bullhead model:Nexus_5X device:bullhead transport_id:1"; - DeviceData device = DeviceData.CreateFromAdbData(data); - Assert.Equal("00bc13bcf4bacc62", device.Serial); - Assert.Equal("bullhead", device.Product); - Assert.Equal("Nexus_5X", device.Model); - Assert.Equal("bullhead", device.Name); - Assert.Equal("1", device.TransportId); - Assert.Equal(DeviceState.Online, device.State); + Assert.Equal(serial, device.Serial); + Assert.Equal(product, device.Product); + Assert.Equal(model, device.Model); + Assert.Equal(name, device.Name); + Assert.Equal(transportId, device.TransportId); + Assert.Equal(DeviceState.Online, device.State); Assert.Equal(string.Empty, device.Usb); } [Fact] - public void CreateFromDeviceDataConnecting() + public void CreateFromDeviceDataConnectingTest() { string data = "00bc13bcf4bacc62 connecting"; @@ -230,9 +202,8 @@ public void CreateFromDeviceDataConnecting() Assert.Equal(string.Empty, device.Model); Assert.Equal(string.Empty, device.Name); Assert.Equal(string.Empty, device.TransportId); - Assert.Equal(DeviceState.Unknown, device.State); + Assert.Equal(DeviceState.Unknown, device.State); Assert.Equal(string.Empty, device.Usb); } - } } diff --git a/AdvancedSharpAdbClient.Tests/Models/ForwardDataTests.cs b/AdvancedSharpAdbClient.Tests/Models/ForwardDataTests.cs index e81e3c92..ae75dcf9 100644 --- a/AdvancedSharpAdbClient.Tests/Models/ForwardDataTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/ForwardDataTests.cs @@ -8,7 +8,7 @@ namespace AdvancedSharpAdbClient.Tests public class ForwardDataTests { [Fact] - public void SpecTests() + public void SpecTest() { ForwardData data = new() { diff --git a/AdvancedSharpAdbClient.Tests/Models/ForwardSpecTests.cs b/AdvancedSharpAdbClient.Tests/Models/ForwardSpecTests.cs index a2612638..b3659a74 100644 --- a/AdvancedSharpAdbClient.Tests/Models/ForwardSpecTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/ForwardSpecTests.cs @@ -71,7 +71,7 @@ public void ParseInvalidProtocolTest() => _ = Assert.Throws(() => ForwardSpec.Parse("xyz:1234")); [Fact] - public void ToStringInvalidProtocol() + public void ToStringInvalidProtocolTest() { ForwardSpec spec = new() { diff --git a/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs b/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs index 27a7c1df..4912f9f8 100644 --- a/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs +++ b/AdvancedSharpAdbClient.Tests/Models/FramebufferHeaderTests.cs @@ -35,7 +35,7 @@ public void ReadFramebufferTest() } [Fact] - public void ReadFramebufferv2Test() + public void ReadFramebufferV2Test() { byte[] data = File.ReadAllBytes("Assets/framebufferheader-v2.bin"); diff --git a/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs b/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs index 751f1247..a7e804de 100644 --- a/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs +++ b/AdvancedSharpAdbClient.Tests/Receivers/ConsoleOutputReceiverTests.cs @@ -24,51 +24,39 @@ public void ToStringTest() ignoreLineEndingDifferences: true); } - [Fact] - public void ToStringIgnoredLineTest() + [Theory] + [InlineData("#Hello, World!", "See you!", "See you!\r\n")] + [InlineData("Hello, World!", "$See you!", "Hello, World!\r\n")] + public void ToStringIgnoredLineTest(string line1, string line2, string result) { ConsoleOutputReceiver receiver = new(); - receiver.AddOutput("#Hello, World!"); - receiver.AddOutput("See you!"); - - receiver.Flush(); - - Assert.Equal("See you!\r\n", - receiver.ToString(), - ignoreLineEndingDifferences: true); - } - - [Fact] - public void ToStringIgnoredLineTest2() - { - ConsoleOutputReceiver receiver = new(); - receiver.AddOutput("Hello, World!"); - receiver.AddOutput("$See you!"); + receiver.AddOutput(line1); + receiver.AddOutput(line2); receiver.Flush(); - Assert.Equal("Hello, World!\r\n", + Assert.Equal(result, receiver.ToString(), ignoreLineEndingDifferences: true); } [Fact] - public void TrowOnErrorTest() + public void ThrowOnErrorTest() { - AssertTrowsException("/dev/test: not found"); - AssertTrowsException("No such file or directory"); - AssertTrowsException("Unknown option -h"); - AssertTrowsException("/dev/test: Aborting."); - AssertTrowsException("/dev/test: applet not found"); - AssertTrowsException("/dev/test: permission denied"); - AssertTrowsException("/dev/test: access denied"); + AssertThrowsException("/dev/test: not found"); + AssertThrowsException("No such file or directory"); + AssertThrowsException("Unknown option -h"); + AssertThrowsException("/dev/test: Aborting."); + AssertThrowsException("/dev/test: applet not found"); + AssertThrowsException("/dev/test: permission denied"); + AssertThrowsException("/dev/test: access denied"); // Should not thrown an exception ConsoleOutputReceiver receiver = new(); receiver.ThrowOnError("Stay calm and watch cat movies."); } - private static void AssertTrowsException(string line) where T : Exception + private static void AssertThrowsException(string line) where T : Exception { ConsoleOutputReceiver receiver = new(); _ = Assert.Throws(() => receiver.ThrowOnError(line)); diff --git a/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs b/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs index 37a43883..67329e67 100644 --- a/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs +++ b/AdvancedSharpAdbClient.Tests/SocketBasedTests.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Threading.Tasks; using Xunit; namespace AdvancedSharpAdbClient @@ -72,22 +73,14 @@ protected SocketBasedTests(bool integrationTest, bool doDispose) /// /// When running as an integration test, all three parameters, , /// and are used to validate - /// that the traffic we simulate in the unit tests matches the trafic that is actually sent + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent /// over the wire. /// /// - /// - /// The messages that the ADB sever should send. - /// - /// - /// The messages that should follow the . - /// - /// - /// The requests the client should send. - /// - /// - /// The test to run. - /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The test to run. protected void RunTest( IEnumerable responses, IEnumerable responseMessages, @@ -95,6 +88,28 @@ protected void RunTest( Action test) => RunTest(responses, responseMessages, requests, null, null, null, null, null, test); + /// + /// + /// Runs an ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The of . + /// The test to run. protected void RunTest( IEnumerable responses, IEnumerable responseMessages, @@ -103,6 +118,31 @@ protected void RunTest( Action test) => RunTest(responses, responseMessages, requests, null, null, null, null, shellStream, test); + /// + /// + /// Runs an ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The requests the client should send. + /// The messages that the ADB sever should send. + /// The of data which the ADB sever should send. + /// The of data which the client should send. + /// The test to run. protected void RunTest( IEnumerable responses, IEnumerable responseMessages, @@ -123,6 +163,32 @@ protected void RunTest( null, test); + /// + /// + /// Runs an ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The requests the client should send. + /// The messages that the ADB sever should send. + /// The of data which the ADB sever should send. + /// The of data which the client should send. + /// The of . + /// The test to run. protected void RunTest( IEnumerable responses, IEnumerable responseMessages, @@ -262,6 +328,282 @@ protected void RunTest( } } + /// + /// + /// Runs an async ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The test to run. + /// A which represents the asynchronous operation. + protected Task RunTestAsync( + IEnumerable responses, + IEnumerable responseMessages, + IEnumerable requests, + Func test) => + RunTestAsync(responses, responseMessages, requests, null, null, null, null, null, test); + + /// + /// + /// Runs an async ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The of . + /// The test to run. + /// A which represents the asynchronous operation. + protected Task RunTestAsync( + IEnumerable responses, + IEnumerable responseMessages, + IEnumerable requests, + Stream shellStream, + Func test) => + RunTestAsync(responses, responseMessages, requests, null, null, null, null, shellStream, test); + + /// + /// + /// Runs an async ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The requests the client should send. + /// The messages that the ADB sever should send. + /// The of data which the ADB sever should send. + /// The of data which the client should send. + /// The test to run. + /// A which represents the asynchronous operation. + protected Task RunTestAsync( + IEnumerable responses, + IEnumerable responseMessages, + IEnumerable requests, + IEnumerable<(SyncCommand, string)> syncRequests, + IEnumerable syncResponses, + IEnumerable syncDataReceived, + IEnumerable syncDataSent, + Func test) => + RunTestAsync( + responses, + responseMessages, + requests, + syncRequests, + syncResponses, + syncDataReceived, + syncDataSent, + null, + test); + + /// + /// + /// Runs an async ADB helper test, either as a unit test or as an integration test. + /// + /// + /// When running as a unit test, the and + /// are used by the to mock the responses an actual device + /// would send; and the parameter is used to ensure the code + /// did send the correct requests to the device. + /// + /// + /// When running as an integration test, all three parameters, , + /// and are used to validate + /// that the traffic we simulate in the unit tests matches the traffic that is actually sent + /// over the wire. + /// + /// + /// The messages that the ADB sever should send. + /// The messages that should follow the . + /// The requests the client should send. + /// The requests the client should send. + /// The messages that the ADB sever should send. + /// The of data which the ADB sever should send. + /// The of data which the client should send. + /// The of . + /// The test to run. + /// A which represents the asynchronous operation. + protected async Task RunTestAsync( + IEnumerable responses, + IEnumerable responseMessages, + IEnumerable requests, + IEnumerable<(SyncCommand, string)> syncRequests, + IEnumerable syncResponses, + IEnumerable syncDataReceived, + IEnumerable syncDataSent, + Stream shellStream, + Func test) + { + // If we are running unit tests, we need to mock all the responses + // that are sent by the device. Do that now. + if (!IntegrationTest) + { + Socket.ShellStream = shellStream; + + foreach (AdbResponse response in responses) + { + Socket.Responses.Enqueue(response); + } + + foreach (string responseMessage in responseMessages) + { + Socket.ResponseMessages.Enqueue(responseMessage); + } + + if (syncResponses != null) + { + foreach (SyncCommand syncResponse in syncResponses) + { + Socket.SyncResponses.Enqueue(syncResponse); + } + } + + if (syncDataReceived != null) + { + foreach (byte[] syncDatum in syncDataReceived) + { + Socket.SyncDataReceived.Enqueue(syncDatum); + } + } + } + + Exception exception = null; + + try + { + await test(); + } + catch (AggregateException ex) + { + exception = ex.InnerExceptions.Count == 1 ? ex.InnerException : ex; + } + catch (Exception ex) + { + exception = ex; + } + + if (!IntegrationTest) + { + // If we are running unit tests, we need to make sure all messages + // were read, and the correct request was sent. + + // Make sure the messages were read + Assert.Empty(Socket.ResponseMessages); + Assert.Empty(Socket.Responses); + Assert.Empty(Socket.SyncResponses); + Assert.Empty(Socket.SyncDataReceived); + + // Make sure a request was sent + Assert.Equal(requests.ToList(), Socket.Requests); + + if (syncRequests != null) + { + Assert.Equal(syncRequests.ToList(), Socket.SyncRequests); + } + else + { + Assert.Empty(Socket.SyncRequests); + } + + if (syncDataSent != null) + { + AssertEqual(syncDataSent.ToList(), Socket.SyncDataSent.ToList()); + } + else + { + Assert.Empty(Socket.SyncDataSent); + } + } + else + { + // Make sure the traffic sent on the wire matches the traffic + // we have defined in our unit test. + Assert.Equal(requests.ToList(), Socket.Requests); + + if (syncRequests != null) + { + Assert.Equal(syncRequests.ToList(), Socket.SyncRequests); + } + else + { + Assert.Empty(Socket.SyncRequests); + } + + Assert.Equal(responses.ToList(), Socket.Responses); + Assert.Equal(responseMessages.ToList(), Socket.ResponseMessages); + + if (syncResponses != null) + { + Assert.Equal(syncResponses.ToList(), Socket.SyncResponses); + } + else + { + Assert.Empty(Socket.SyncResponses); + } + + if (syncDataReceived != null) + { + AssertEqual(syncDataReceived.ToList(), Socket.SyncDataReceived.ToList()); + } + else + { + Assert.Empty(Socket.SyncDataReceived); + } + + if (syncDataSent != null) + { + AssertEqual(syncDataSent.ToList(), Socket.SyncDataSent.ToList()); + } + else + { + Assert.Empty(Socket.SyncDataSent); + } + } + + if (exception != null) + { + throw exception; + } + } + protected static IEnumerable Requests(params string[] requests) => requests; protected static IEnumerable ResponseMessages(params string[] requests) => requests; diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs new file mode 100644 index 00000000..e693e4cc --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class SyncServiceTests + { + [Fact] + public async void StatAsyncTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + FileStatistics value = null; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + Requests("host:transport:169.254.109.177:5555", "sync:"), + SyncRequests(SyncCommand.STAT, "/fstab.donatello"), + new SyncCommand[] { SyncCommand.STAT }, + new byte[][] { new byte[] { 160, 129, 0, 0, 85, 2, 0, 0, 0, 0, 0, 0 } }, + null, + async () => + { + using SyncService service = new(Socket, device); + value = await service.StatAsync("/fstab.donatello"); + }); + + Assert.NotNull(value); + Assert.Equal(UnixFileMode.Regular, value.FileMode & UnixFileMode.TypeMask); + Assert.Equal(597, value.Size); + Assert.Equal(DateTimeHelper.Epoch.ToLocalTime(), value.Time); + } + + [Fact] + public async void GetListingAsyncTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + List value = null; + + await RunTestAsync( + OkResponses(2), + ResponseMessages(".", "..", "sdcard0", "emulated"), + Requests("host:transport:169.254.109.177:5555", "sync:"), + SyncRequests(SyncCommand.LIST, "/storage"), + new SyncCommand[] { SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DENT, SyncCommand.DONE }, + new byte[][] + { + new byte[] { 233, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86 }, + new byte[] { 237, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86 }, + new byte[] { 255, 161, 0, 0, 24, 0, 0, 0, 152, 130, 56, 86 }, + new byte[] { 109, 65, 0, 0, 0, 0, 0, 0, 152, 130, 56, 86 } + }, + null, + async () => + { + using SyncService service = new(Socket, device); + value = (await service.GetDirectoryListingAsync("/storage")).ToList(); + }); + + Assert.Equal(4, value.Count); + + DateTime time = new DateTime(2015, 11, 3, 9, 47, 4, DateTimeKind.Utc).ToLocalTime(); + + FileStatistics dir = value[0]; + Assert.Equal(".", dir.Path); + Assert.Equal((UnixFileMode)16873, dir.FileMode); + Assert.Equal(0, dir.Size); + Assert.Equal(time, dir.Time); + + FileStatistics parentDir = value[1]; + Assert.Equal("..", parentDir.Path); + Assert.Equal((UnixFileMode)16877, parentDir.FileMode); + Assert.Equal(0, parentDir.Size); + Assert.Equal(time, parentDir.Time); + + FileStatistics sdcard0 = value[2]; + Assert.Equal("sdcard0", sdcard0.Path); + Assert.Equal((UnixFileMode)41471, sdcard0.FileMode); + Assert.Equal(24, sdcard0.Size); + Assert.Equal(time, sdcard0.Time); + + FileStatistics emulated = value[3]; + Assert.Equal("emulated", emulated.Path); + Assert.Equal((UnixFileMode)16749, emulated.FileMode); + Assert.Equal(0, emulated.Size); + Assert.Equal(time, emulated.Time); + } + + [Fact] + public async void PullAsyncTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + using MemoryStream stream = new(); + byte[] content = File.ReadAllBytes("Assets/fstab.bin"); + byte[] contentLength = BitConverter.GetBytes(content.Length); + + await RunTestAsync( + OkResponses(2), + ResponseMessages(), + Requests("host:transport:169.254.109.177:5555", "sync:"), + SyncRequests(SyncCommand.STAT, "/fstab.donatello").Union(SyncRequests(SyncCommand.RECV, "/fstab.donatello")), + new SyncCommand[] { SyncCommand.STAT, SyncCommand.DATA, SyncCommand.DONE }, + new byte[][] + { + new byte[] { 160, 129, 0, 0, 85, 2, 0, 0, 0, 0, 0, 0 }, + contentLength, + content + }, + null, + async () => + { + using SyncService service = new(Socket, device); + await service.PullAsync("/fstab.donatello", stream, null, CancellationToken.None); + }); + + // Make sure the data that has been sent to the stream is the expected data + Assert.Equal(content, stream.ToArray()); + } + + [Fact] + public async void PushAsyncTest() + { + DeviceData device = new() + { + Serial = "169.254.109.177:5555", + State = DeviceState.Online + }; + + Stream stream = File.OpenRead("Assets/fstab.bin"); + byte[] content = File.ReadAllBytes("Assets/fstab.bin"); + List contentMessage = new(); + contentMessage.AddRange(SyncCommandConverter.GetBytes(SyncCommand.DATA)); + contentMessage.AddRange(BitConverter.GetBytes(content.Length)); + contentMessage.AddRange(content); + + await RunTestAsync( + OkResponses(2), + ResponseMessages(), + Requests("host:transport:169.254.109.177:5555", "sync:"), + SyncRequests( + SyncCommand.SEND, "/sdcard/test,644", + SyncCommand.DONE, "1446505200"), + new SyncCommand[] { SyncCommand.OKAY }, + null, + new byte[][] + { + contentMessage.ToArray() + }, + async () => + { + using SyncService service = new(Socket, device); + await service.PushAsync(stream, "/sdcard/test", 0644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), null, CancellationToken.None); + }); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index 693cd22a..42589feb 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -10,7 +10,7 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class SyncServiceTests : SocketBasedTests + public partial class SyncServiceTests : SocketBasedTests { // Toggle the integration test flag to true to run on an actual adb server // (and to build/validate the test cases), set to false to use the mocked @@ -120,7 +120,7 @@ public void PullTest() State = DeviceState.Online }; - MemoryStream stream = new(); + using MemoryStream stream = new(); byte[] content = File.ReadAllBytes("Assets/fstab.bin"); byte[] contentLength = BitConverter.GetBytes(content.Length); diff --git a/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs b/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs new file mode 100644 index 00000000..e33f62cf --- /dev/null +++ b/AdvancedSharpAdbClient.Tests/TcpSocketTests.Async.cs @@ -0,0 +1,29 @@ +using System.Net; +using System.Net.Sockets; +using System.Text; +using Xunit; + +namespace AdvancedSharpAdbClient.Tests +{ + public partial class TcpSocketTests + { + [Fact] + public async void LifecycleAsyncTest() + { + TcpSocket socket = new(); + Assert.False(socket.Connected); + + socket.Connect(new DnsEndPoint("www.bing.com", 80)); + Assert.True(socket.Connected); + + byte[] data = Encoding.ASCII.GetBytes("GET / HTTP/1.1\n\n"); + await socket.SendAsync(data, 0, data.Length, SocketFlags.None); + + byte[] responseData = new byte[128]; + await socket.ReceiveAsync(responseData, 0, responseData.Length, SocketFlags.None); + + _ = Encoding.ASCII.GetString(responseData); + socket.Dispose(); + } + } +} diff --git a/AdvancedSharpAdbClient.Tests/TcpSocketTests.cs b/AdvancedSharpAdbClient.Tests/TcpSocketTests.cs index 6993e2e4..4dcd5b9f 100644 --- a/AdvancedSharpAdbClient.Tests/TcpSocketTests.cs +++ b/AdvancedSharpAdbClient.Tests/TcpSocketTests.cs @@ -10,7 +10,7 @@ namespace AdvancedSharpAdbClient.Tests /// /// Tests the class. /// - public class TcpSocketTests + public partial class TcpSocketTests { [Fact] public void LifecycleTest() diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index 5d864095..cfcb2e3c 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -12,6 +12,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Xml; @@ -240,7 +241,11 @@ public async Task GetFrameBufferAsync(DeviceData device, Cancellati } /// - public async Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken = default, params LogId[] logNames) + public Task RunLogServiceAsync(DeviceData device, Action messageSink, params LogId[] logNames) => + RunLogServiceAsync(device, messageSink, default, logNames); + + /// + public async Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) { ExceptionExtensions.ThrowIfNull(messageSink); @@ -271,7 +276,7 @@ public async Task RunLogServiceAsync(DeviceData device, Action message try { - entry = await reader.ReadEntry(cancellationToken).ConfigureAwait(false); + entry = await reader.ReadEntryAsync(cancellationToken).ConfigureAwait(false); } catch (EndOfStreamException) { @@ -339,10 +344,10 @@ public async Task DisconnectAsync(DnsEndPoint endpoint, CancellationToke } /// - public Task RootAsync(DeviceData device, CancellationToken cancellationToken) => RootAsync("root:", device, cancellationToken); + public Task RootAsync(DeviceData device, CancellationToken cancellationToken = default) => RootAsync("root:", device, cancellationToken); /// - public Task UnrootAsync(DeviceData device, CancellationToken cancellationToken) => RootAsync("unroot:", device, cancellationToken); + public Task UnrootAsync(DeviceData device, CancellationToken cancellationToken = default) => RootAsync("unroot:", device, cancellationToken); /// /// Restarts the ADB daemon running on the device with or without root privileges. @@ -350,7 +355,7 @@ public async Task DisconnectAsync(DnsEndPoint endpoint, CancellationToke /// The command of root or unroot. /// The device on which to restart ADB with root privileges. /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. + /// A which return the results from adb. protected async Task RootAsync(string request, DeviceData device, CancellationToken cancellationToken = default) { EnsureDevice(device); @@ -400,14 +405,13 @@ public async Task InstallAsync(DeviceData device, Stream apk, CancellationToken } StringBuilder requestBuilder = new(); - _ = requestBuilder.Append("exec:cmd package 'install' "); + _ = requestBuilder.Append("exec:cmd package 'install'"); if (arguments != null) { foreach (string argument in arguments) { - _ = requestBuilder.Append(' '); - _ = requestBuilder.Append(argument); + _ = requestBuilder.Append($" {argument}"); } } @@ -435,7 +439,7 @@ public async Task InstallAsync(DeviceData device, Stream apk, CancellationToken await socket.SendAsync(buffer, read, cancellationToken); } - read = await socket.ReadAsync(buffer, buffer.Length, cancellationToken); + read = await socket.ReadAsync(buffer, cancellationToken); string value = Encoding.UTF8.GetString(buffer, 0, read); if (!value.Contains("Success")) @@ -458,12 +462,12 @@ public async Task InstallMultipleAsync(DeviceData device, IEnumerable sp string session = await InstallCreateAsync(device, packageName, cancellationToken, arguments); int i = 0; - foreach (Stream splitAPK in splitAPKs) + IEnumerable tasks = splitAPKs.Select(async (splitAPK) => { if (splitAPK == null || !splitAPK.CanRead || !splitAPK.CanSeek) { Debug.WriteLine("The apk stream must be a readable and seekable stream"); - continue; + return; } try @@ -474,6 +478,10 @@ public async Task InstallMultipleAsync(DeviceData device, IEnumerable sp { Debug.WriteLine(ex.Message); } + }); + foreach (Task task in tasks) + { + await task; } await InstallCommitAsync(device, session, cancellationToken); @@ -500,12 +508,12 @@ public async Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnume await InstallWriteAsync(device, baseAPK, nameof(baseAPK), session, cancellationToken); int i = 0; - foreach (Stream splitAPK in splitAPKs) + IEnumerable tasks = splitAPKs.Select(async (splitAPK) => { if (splitAPK == null || !splitAPK.CanRead || !splitAPK.CanSeek) { Debug.WriteLine("The apk stream must be a readable and seekable stream"); - continue; + return; } try @@ -516,6 +524,10 @@ public async Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnume { Debug.WriteLine(ex.Message); } + }); + foreach (Task task in tasks) + { + await task; } await InstallCommitAsync(device, session, cancellationToken); @@ -532,15 +544,14 @@ public async Task InstallCreateAsync(DeviceData device, string packageNa EnsureDevice(device); StringBuilder requestBuilder = new(); - _ = requestBuilder.Append("exec:cmd package 'install-create' "); - _ = requestBuilder.Append(packageName.IsNullOrWhiteSpace() ? string.Empty : $"-p {packageName}"); + _ = requestBuilder.Append("exec:cmd package 'install-create'"); + _ = requestBuilder.Append(packageName.IsNullOrWhiteSpace() ? string.Empty : $" -p {packageName}"); if (arguments != null) { foreach (string argument in arguments) { - _ = requestBuilder.Append(' '); - _ = requestBuilder.Append(argument); + _ = requestBuilder.Append($" {argument}"); } } @@ -589,7 +600,7 @@ public async Task InstallWriteAsync(DeviceData device, Stream apk, string apkNam ExceptionExtensions.ThrowIfNull(apkName); StringBuilder requestBuilder = new(); - requestBuilder.Append($"exec:cmd package 'install-write' "); + requestBuilder.Append($"exec:cmd package 'install-write'"); // add size parameter [required for streaming installs] // do last to override any user specified value @@ -617,7 +628,7 @@ public async Task InstallWriteAsync(DeviceData device, Stream apk, string apkNam await socket.SendAsync(buffer, read, cancellationToken); } - read = await socket.ReadAsync(buffer, buffer.Length, cancellationToken); + read = await socket.ReadAsync(buffer, cancellationToken); string value = Encoding.UTF8.GetString(buffer, 0, read); if (!value.Contains("Success")) @@ -669,7 +680,7 @@ public async Task> GetFeatureSetAsync(DeviceData device, Can AdbResponse response = await socket.ReadAdbResponseAsync(cancellationToken); string features = await socket.ReadStringAsync(cancellationToken); - IEnumerable featureList = features.Split(new char[] { '\n', ',' }); + IEnumerable featureList = features.Trim().Split(new char[] { '\n', ',' }); return featureList; } @@ -858,16 +869,11 @@ public async Task GetAppStatusAsync(DeviceData device, string package // Check if the app is running in background bool isAppRunning = await IsAppRunningAsync(device, packageName, cancellationToken); - if (isAppRunning) - { - return AppStatus.Background; - } - - return AppStatus.Stopped; + return isAppRunning ? AppStatus.Background : AppStatus.Stopped; } /// - public async Task FindElementAsync(DeviceData device, string xpath, CancellationToken cancellationToken = default) + public async Task FindElementAsync(DeviceData device, string xpath = "hierarchy/node", CancellationToken cancellationToken = default) { try { @@ -879,17 +885,10 @@ public async Task FindElementAsync(DeviceData device, string xpath, Can XmlNode xmlNode = doc.SelectSingleNode(xpath); if (xmlNode != null) { - string bounds = xmlNode.Attributes["bounds"].Value; - if (bounds != null) + Element element = Element.FromXmlNode(this, device, xmlNode); + if (element != null) { - int[] cords = bounds.Replace("][", ",").Replace("[", "").Replace("]", "").Split(',').Select(int.Parse).ToArray(); // x1, y1, x2, y2 - Dictionary attributes = new(); - foreach (XmlAttribute at in xmlNode.Attributes) - { - attributes.Add(at.Name, at.Value); - } - Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); - return new Element(this, device, area, attributes); + return element; } } } @@ -913,7 +912,7 @@ public async Task FindElementAsync(DeviceData device, string xpath, Can } /// - public async Task FindElementsAsync(DeviceData device, string xpath, CancellationToken cancellationToken = default) + public async Task> FindElementsAsync(DeviceData device, string xpath = "hierarchy/node", CancellationToken cancellationToken = default) { try { @@ -925,23 +924,16 @@ public async Task FindElementsAsync(DeviceData device, string xpath, XmlNodeList xmlNodes = doc.SelectNodes(xpath); if (xmlNodes != null) { - Element[] elements = new Element[xmlNodes.Count]; - for (int i = 0; i < elements.Length; i++) + List elements = new(xmlNodes.Count); + for (int i = 0; i < xmlNodes.Count; i++) { - string bounds = xmlNodes[i].Attributes["bounds"].Value; - if (bounds != null) + Element element = Element.FromXmlNode(this, device, xmlNodes[i]); + if (element != null) { - int[] cords = bounds.Replace("][", ",").Replace("[", "").Replace("]", "").Split(',').Select(int.Parse).ToArray(); // x1, y1, x2, y2 - Dictionary attributes = new(); - foreach (XmlAttribute at in xmlNodes[i].Attributes) - { - attributes.Add(at.Name, at.Value); - } - Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); - elements[i] = new Element(this, device, area, attributes); + elements.Add(element); } } - return elements.Length == 0 ? null : elements; + return elements.Count == 0 ? null : elements; } } if (cancellationToken == default) @@ -963,6 +955,42 @@ public async Task FindElementsAsync(DeviceData device, string xpath, return null; } +#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) + { + XmlDocument doc = await DumpScreenAsync(device, cancellationToken); + if (doc != null) + { + XmlNodeList xmlNodes = doc.SelectNodes(xpath); + if (xmlNodes != null) + { + bool isBreak = false; + for (int i = 0; i < xmlNodes.Count; i++) + { + Element element = Element.FromXmlNode(this, device, xmlNodes[i]); + if (element != null) + { + isBreak = true; + yield return element; + } + } + if (isBreak) + { + break; + } + } + } + if (cancellationToken == default) + { + break; + } + } + } +#endif + /// public async Task SendKeyEventAsync(DeviceData device, string key, CancellationToken cancellationToken = default) { diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 624bfd92..e22adfe6 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using AdvancedSharpAdbClient.Logs; using System.IO; using System.Linq; using System.Net; @@ -339,6 +340,10 @@ public void ExecuteRemoteCommand(string command, DeviceData device, IShellOutput receiver?.AddOutput(line); } } + catch (Exception e) + { + throw new ShellCommandUnresponsiveException(e); + } finally { receiver?.Flush(); @@ -365,6 +370,56 @@ public Framebuffer GetFrameBuffer(DeviceData device) return framebuffer; } + /// + public void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames) + { + ExceptionExtensions.ThrowIfNull(messageSink); + + EnsureDevice(device); + + // The 'log' service has been deprecated, see + // https://android.googlesource.com/platform/system/core/+/7aa39a7b199bb9803d3fd47246ee9530b4a96177 + using IAdbSocket socket = adbSocketFactory(EndPoint); + socket.SetDevice(device); + + StringBuilder request = new(); + request.Append("shell:logcat -B"); + + foreach (LogId logName in logNames) + { + request.Append($" -b {logName.ToString().ToLowerInvariant()}"); + } + + socket.SendAdbRequest(request.ToString()); + AdbResponse response = socket.ReadAdbResponse(); + + using Stream stream = socket.GetShellStream(); + LogReader reader = new(stream); + + while (true) + { + LogEntry entry = null; + + try + { + entry = reader.ReadEntry(); + } + catch (EndOfStreamException) + { + // This indicates the end of the stream; the entry will remain null. + } + + if (entry != null) + { + messageSink(entry); + } + else + { + break; + } + } + } + /// public void Reboot(string into, DeviceData device) { @@ -475,14 +530,13 @@ public void Install(DeviceData device, Stream apk, params string[] arguments) } StringBuilder requestBuilder = new(); - _ = requestBuilder.Append("exec:cmd package 'install' "); + _ = requestBuilder.Append("exec:cmd package 'install'"); if (arguments != null) { foreach (string argument in arguments) { - _ = requestBuilder.Append(' '); - _ = requestBuilder.Append(argument); + _ = requestBuilder.Append($" {argument}"); } } @@ -588,15 +642,14 @@ public string InstallCreate(DeviceData device, string packageName = null, params EnsureDevice(device); StringBuilder requestBuilder = new(); - _ = requestBuilder.Append("exec:cmd package 'install-create' "); - _ = requestBuilder.Append(packageName.IsNullOrWhiteSpace() ? string.Empty : $"-p {packageName}"); + _ = requestBuilder.Append("exec:cmd package 'install-create'"); + _ = requestBuilder.Append(packageName.IsNullOrWhiteSpace() ? string.Empty : $" -p {packageName}"); if (arguments != null) { foreach (string argument in arguments) { - _ = requestBuilder.Append(' '); - _ = requestBuilder.Append(argument); + _ = requestBuilder.Append($" {argument}"); } } @@ -636,7 +689,7 @@ public void InstallWrite(DeviceData device, Stream apk, string apkName, string s ExceptionExtensions.ThrowIfNull(apkName); StringBuilder requestBuilder = new(); - requestBuilder.Append($"exec:cmd package 'install-write' "); + requestBuilder.Append($"exec:cmd package 'install-write'"); // add size parameter [required for streaming installs] // do last to override any user specified value @@ -697,7 +750,7 @@ public IEnumerable GetFeatureSet(DeviceData device) AdbResponse response = socket.ReadAdbResponse(); string features = socket.ReadString(); - IEnumerable featureList = features.Split(new char[] { '\n', ',' }); + IEnumerable featureList = features.Trim().Split(new char[] { '\n', ',' }); return featureList; } @@ -843,16 +896,11 @@ public AppStatus GetAppStatus(DeviceData device, string packageName) // Check if the app is running in background bool isAppRunning = IsAppRunning(device, packageName); - if (isAppRunning) - { - return AppStatus.Background; - } - - return AppStatus.Stopped; + return isAppRunning ? AppStatus.Background : AppStatus.Stopped; } /// - public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = default) + public Element FindElement(DeviceData device, string xpath = "hierarchy/node", TimeSpan timeout = default) { EnsureDevice(device); Stopwatch stopwatch = new(); @@ -865,17 +913,10 @@ public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = d XmlNode xmlNode = doc.SelectSingleNode(xpath); if (xmlNode != null) { - string bounds = xmlNode.Attributes["bounds"].Value; - if (bounds != null) + Element element = Element.FromXmlNode(this, device, xmlNode); + if (element != null) { - int[] cords = bounds.Replace("][", ",").Replace("[", "").Replace("]", "").Split(',').Select(int.Parse).ToArray(); // x1, y1, x2, y2 - Dictionary attributes = new(); - foreach (XmlAttribute at in xmlNode.Attributes) - { - attributes.Add(at.Name, at.Value); - } - Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); - return new Element(this, device, area, attributes); + return element; } } } @@ -888,7 +929,7 @@ public Element FindElement(DeviceData device, string xpath, TimeSpan timeout = d } /// - public Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout = default) + public IEnumerable FindElements(DeviceData device, string xpath = "hierarchy/node", TimeSpan timeout = default) { EnsureDevice(device); Stopwatch stopwatch = new(); @@ -901,23 +942,20 @@ public Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout XmlNodeList xmlNodes = doc.SelectNodes(xpath); if (xmlNodes != null) { - Element[] elements = new Element[xmlNodes.Count]; - for (int i = 0; i < elements.Length; i++) + bool isBreak = false; + for (int i = 0; i < xmlNodes.Count; i++) { - string bounds = xmlNodes[i].Attributes["bounds"].Value; - if (bounds != null) + Element element = Element.FromXmlNode(this, device, xmlNodes[i]); + if (element != null) { - int[] cords = bounds.Replace("][", ",").Replace("[", "").Replace("]", "").Split(',').Select(int.Parse).ToArray(); // x1, y1, x2, y2 - Dictionary attributes = new(); - foreach (XmlAttribute at in xmlNodes[i].Attributes) - { - attributes.Add(at.Name, at.Value); - } - Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); - elements[i] = new Element(this, device, area, attributes); + isBreak = true; + yield return element; } } - return elements.Length == 0 ? null : elements; + if (isBreak) + { + break; + } } } if (timeout == TimeSpan.Zero) @@ -925,7 +963,6 @@ public Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout break; } } - return null; } /// diff --git a/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs b/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs new file mode 100644 index 00000000..6ddfd676 --- /dev/null +++ b/AdvancedSharpAdbClient/AdbCommandLineClient.Async.cs @@ -0,0 +1,134 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using AdvancedSharpAdbClient.Exceptions; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +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 async Task GetVersionAsync(CancellationToken cancellationToken = default) + { + // Run the adb.exe version command and capture the output. + List standardOutput = new(); + + await RunAdbProcessAsync("version", null, standardOutput, cancellationToken); + + // Parse the output to get the version. + Version version = GetVersionFromOutput(standardOutput) ?? throw new AdbException($"The version of the adb executable at {AdbPath} could not be determined."); + + if (version < AdbServer.RequiredAdbVersion) + { + AdbException ex = new($"Required minimum version of adb: {AdbServer.RequiredAdbVersion}. Current version is {version}"); +#if HAS_LOGGER + logger.LogError(ex, ex.Message); +#endif + throw ex; + } + + 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 async Task StartServerAsync(CancellationToken cancellationToken = default) + { + int status = await RunAdbProcessInnerAsync("start-server", null, null, cancellationToken); + + if (status == 0) + { + return; + } + +#if HAS_PROCESS && !WINDOWS_UWP + // Starting the adb server failed for whatever reason. This can happen if adb.exe + // is running but is not accepting requests. In that case, try to kill it & start again. + // It kills all processes named "adb", so let's hope nobody else named their process that way. + foreach (Process adbProcess in Process.GetProcessesByName("adb")) + { + try + { + adbProcess.Kill(); + } + catch (Win32Exception) + { + // The associated process could not be terminated + // or + // The process is terminating. + } + catch (InvalidOperationException) + { + // The process has already exited. + // There is no process associated with this Process object. + } + } +#endif + + // Try again. This time, we don't call "Inner", and an exception will be thrown if the start operation fails + // again. We'll let that exception bubble up the stack. + await RunAdbProcessAsync("start-server", null, null, cancellationToken); + } + + /// + /// 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. + /// A list in which to store the standard error output. Each line is added as a new entry. + /// This value can be if you are not interested in the standard error. + /// A list in which to store the standard output. Each line is added as a new entry. + /// This value can be if you are not interested in the standard output. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + /// Use this command only for adb commands that return immediately, such as + /// adb version. This operation times out after 5 seconds. + /// The process exited with an exit code other than 0. + protected virtual async Task RunAdbProcessAsync(string command, List errorOutput, List standardOutput, CancellationToken cancellationToken = default) + { + int status = await RunAdbProcessInnerAsync(command, errorOutput, standardOutput, cancellationToken); + + if (status != 0) + { + throw new AdbException($"The adb process returned error code {status} when running command {command}"); + } + } + + /// + /// 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. + /// A list in which to store the standard error output. Each line is added as a new entry. + /// This value can be if you are not interested in the standard error. + /// A list in which to store the standard output. Each line is added as a new entry. + /// This value can be if you are not interested in the standard output. + /// A which can be used to cancel the asynchronous operation. + /// A which return the return code of the adb process. + /// Use this command only for adb commands that return immediately, such as + /// adb version. This operation times out after 5 seconds. + protected virtual async Task RunAdbProcessInnerAsync(string command, List errorOutput, List standardOutput, CancellationToken cancellationToken = default) + { + ExceptionExtensions.ThrowIfNull(command); + + int status = await CrossPlatformFunc.RunProcessAsync(AdbPath, command, errorOutput, standardOutput, cancellationToken); + + return status; + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/AdbCommandLineClient.cs b/AdvancedSharpAdbClient/AdbCommandLineClient.cs index 894bc984..1a733b0e 100644 --- a/AdvancedSharpAdbClient/AdbCommandLineClient.cs +++ b/AdvancedSharpAdbClient/AdbCommandLineClient.cs @@ -128,7 +128,7 @@ public void StartServer() return; } -#if HAS_PROCESS +#if HAS_PROCESS && !WINDOWS_UWP // Starting the adb server failed for whatever reason. This can happen if adb.exe // is running but is not accepting requests. In that case, try to kill it & start again. // It kills all processes named "adb", so let's hope nobody else named their process that way. diff --git a/AdvancedSharpAdbClient/AdbServer.Async.cs b/AdvancedSharpAdbClient/AdbServer.Async.cs new file mode 100644 index 00000000..51f36bd1 --- /dev/null +++ b/AdvancedSharpAdbClient/AdbServer.Async.cs @@ -0,0 +1,148 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using AdvancedSharpAdbClient.Exceptions; +using System; +using System.Net.Sockets; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial class AdbServer + { + /// + public async Task StartServerAsync(string adbPath, bool restartServerIfNewer, CancellationToken cancellationToken = default) + { + AdbServerStatus serverStatus = await GetStatusAsync(cancellationToken); + Version commandLineVersion = null; + + IAdbCommandLineClient commandLineClient = adbCommandLineClientFactory(adbPath); + + if (commandLineClient.IsValidAdbFile(adbPath)) + { + cachedAdbPath = adbPath; + commandLineVersion = await commandLineClient.GetVersionAsync(cancellationToken); + } + + // If the server is running, and no adb path is provided, check if we have the minimum version + if (adbPath == null) + { + 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."); + } + + if (serverStatus.IsRunning + && ((serverStatus.Version < RequiredAdbVersion) + || ((serverStatus.Version < commandLineVersion) && restartServerIfNewer))) + { + ExceptionExtensions.ThrowIfNull(adbPath); + + await adbClient.KillAdbAsync(cancellationToken); + serverStatus.IsRunning = false; + serverStatus.Version = null; + + await commandLineClient.StartServerAsync(cancellationToken); + return StartServerResult.RestartedOutdatedDaemon; + } + else if (!serverStatus.IsRunning) + { + ExceptionExtensions.ThrowIfNull(adbPath); + + await commandLineClient.StartServerAsync(cancellationToken); + return StartServerResult.Started; + } + else + { + return StartServerResult.AlreadyRunning; + } + } + + /// + public Task RestartServerAsync(CancellationToken cancellationToken = default) => RestartServerAsync(null, cancellationToken); + + /// + public async Task RestartServerAsync(string adbPath, CancellationToken cancellationToken = default) + { + adbPath ??= cachedAdbPath; + + if (!IsValidAdbFile(adbPath)) + { + throw new InvalidOperationException($"The adb server was not started via {nameof(AdbServer)}.{nameof(this.StartServer)} or no path to adb was specified. The adb server cannot be restarted."); + } + + ManualResetEvent manualResetEvent = null; + await Utilities.Run(() => + { + lock (RestartLock) + { + manualResetEvent = new(false); + } + }, cancellationToken); + + _ = Utilities.Run(() => + { + lock (RestartLock) + { + manualResetEvent.WaitOne(); + } + }, cancellationToken); + + StartServerResult result = await StartServerAsync(adbPath, false, cancellationToken); + manualResetEvent.Set(); +#if !NET35 + manualResetEvent.Dispose(); +#else + manualResetEvent.Close(); +#endif + return result; + } + + /// + public async Task GetStatusAsync(CancellationToken cancellationToken = default) + { + // Try to connect to a running instance of the adb server + try + { + int versionCode = await adbClient.GetAdbVersionAsync(cancellationToken); + return new AdbServerStatus(true, new Version(1, 0, versionCode)); + } + catch (AggregateException ex) + { + if (ex.InnerException is SocketException exception) + { + if (exception.SocketErrorCode == SocketError.ConnectionRefused) + { + return new AdbServerStatus(false, null); + } + else + { + // An unexpected exception occurred; re-throw the exception + throw exception; + } + } + else + { + throw; + } + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.ConnectionRefused) + { + return new AdbServerStatus(false, null); + } + else + { + // An unexpected exception occurred; re-throw the exception + throw; + } + } + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/AdbServer.cs b/AdvancedSharpAdbClient/AdbServer.cs index e4fb72a4..fb7470b6 100644 --- a/AdvancedSharpAdbClient/AdbServer.cs +++ b/AdvancedSharpAdbClient/AdbServer.cs @@ -21,7 +21,7 @@ namespace AdvancedSharpAdbClient /// between clients and devices. /// /// - public class AdbServer : IAdbServer + public partial class AdbServer : IAdbServer { /// /// The minimum version of adb.exe that is supported by this library. @@ -45,6 +45,11 @@ public class AdbServer : IAdbServer /// a failure while one or more operations are in progress. internal const int ConnectionReset = 10054; + /// + /// Throws an error if the path does not point to a valid instance of adb.exe. + /// + internal static Func IsValidAdbFile = CrossPlatformFunc.CheckFileExists; + /// /// A lock used to ensure only one caller at a time can attempt to restart adb. /// @@ -150,16 +155,18 @@ public StartServerResult StartServer(string adbPath, bool restartServerIfNewer) } /// - public void RestartServer() + public StartServerResult RestartServer(string adbPath = null) { - if (!CrossPlatformFunc.CheckFileExists(cachedAdbPath)) + adbPath ??= cachedAdbPath; + + if (!IsValidAdbFile(adbPath)) { throw new InvalidOperationException($"The adb server was not started via {nameof(AdbServer)}.{nameof(this.StartServer)} or no path to adb was specified. The adb server cannot be restarted."); } lock (RestartLock) { - _ = StartServer(cachedAdbPath, false); + return StartServer(adbPath, false); } } @@ -170,22 +177,13 @@ public AdbServerStatus GetStatus() try { int versionCode = adbClient.GetAdbVersion(); - - return new AdbServerStatus - { - IsRunning = true, - Version = new Version(1, 0, versionCode) - }; + return new AdbServerStatus(true, new Version(1, 0, versionCode)); } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.ConnectionRefused) { - return new AdbServerStatus - { - IsRunning = false, - Version = null - }; + return new AdbServerStatus(false, null); } else { diff --git a/AdvancedSharpAdbClient/AdbSocket.Async.cs b/AdvancedSharpAdbClient/AdbSocket.Async.cs index aa5cc262..04b3d030 100644 --- a/AdvancedSharpAdbClient/AdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/AdbSocket.Async.cs @@ -87,7 +87,7 @@ public virtual async Task SendAdbRequestAsync(string request, CancellationToken } /// - public virtual Task ReadAsync(byte[] data, CancellationToken cancellationToken = default) => + public virtual Task ReadAsync(byte[] data, CancellationToken cancellationToken = default) => ReadAsync(data, data.Length, cancellationToken); /// @@ -109,9 +109,9 @@ public virtual async Task ReadAsync(byte[] data, int length, CancellationTo try { int left = length - totalRead; - int buflen = left < ReceiveBufferSize ? left : ReceiveBufferSize; + int bufferLength = left < ReceiveBufferSize ? left : ReceiveBufferSize; - count = await socket.ReceiveAsync(data, totalRead, buflen, SocketFlags.None, cancellationToken).ConfigureAwait(false); + count = await socket.ReceiveAsync(data, totalRead, bufferLength, SocketFlags.None, cancellationToken).ConfigureAwait(false); if (count < 0) { @@ -145,7 +145,7 @@ public virtual async Task ReadStringAsync(CancellationToken cancellation { // The first 4 bytes contain the length of the string byte[] reply = new byte[4]; - await ReadAsync(reply, cancellationToken).ConfigureAwait(false); + _ = await ReadAsync(reply, cancellationToken).ConfigureAwait(false); // Convert the bytes to a hex string string lenHex = AdbClient.Encoding.GetString(reply); @@ -153,7 +153,7 @@ public virtual async Task ReadStringAsync(CancellationToken cancellation // And get the string reply = new byte[len]; - await ReadAsync(reply, cancellationToken).ConfigureAwait(false); + _ = await ReadAsync(reply, cancellationToken).ConfigureAwait(false); string value = AdbClient.Encoding.GetString(reply); return value; @@ -164,7 +164,7 @@ public virtual async Task ReadSyncStringAsync(CancellationToken cancella { // The first 4 bytes contain the length of the string byte[] reply = new byte[4]; - await ReadAsync(reply, cancellationToken); + _ = await ReadAsync(reply, cancellationToken); if (!BitConverter.IsLittleEndian) { @@ -175,7 +175,7 @@ public virtual async Task ReadSyncStringAsync(CancellationToken cancella // And get the string reply = new byte[len]; - await ReadAsync(reply, cancellationToken); + _ = await ReadAsync(reply, cancellationToken); string value = AdbClient.Encoding.GetString(reply); return value; @@ -185,7 +185,7 @@ public virtual async Task ReadSyncStringAsync(CancellationToken cancella public virtual async Task ReadSyncResponseAsync(CancellationToken cancellationToken = default) { byte[] data = new byte[4]; - await ReadAsync(data, cancellationToken); + _ = await ReadAsync(data, cancellationToken); return SyncCommandConverter.GetCommand(data); } @@ -268,7 +268,7 @@ protected async Task ReadAdbResponseInnerAsync(CancellationToken ca AdbResponse resp = new(); byte[] reply = new byte[4]; - await ReadAsync(reply, cancellationToken); + _ = await ReadAsync(reply, cancellationToken); resp.IOSuccess = true; diff --git a/AdvancedSharpAdbClient/AdbSocket.cs b/AdvancedSharpAdbClient/AdbSocket.cs index 1fb84ed2..94354076 100644 --- a/AdvancedSharpAdbClient/AdbSocket.cs +++ b/AdvancedSharpAdbClient/AdbSocket.cs @@ -5,8 +5,11 @@ using AdvancedSharpAdbClient.Exceptions; using AdvancedSharpAdbClient.Logs; using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; @@ -191,10 +194,10 @@ public virtual int Read(byte[] data, int length) try { int left = expLen - totalRead; - int buflen = left < ReceiveBufferSize ? left : ReceiveBufferSize; + int bufferLength = left < ReceiveBufferSize ? left : ReceiveBufferSize; - byte[] buffer = new byte[buflen]; - count = socket.Receive(buffer, buflen, SocketFlags.None); + byte[] buffer = new byte[bufferLength]; + count = socket.Receive(buffer, bufferLength, SocketFlags.None); if (count < 0) { #if HAS_LOGGER @@ -373,7 +376,7 @@ protected AdbResponse ReadAdbResponseInner() string message = ReadString(); resp.Message = message; #if HAS_LOGGER - logger.LogError("Got reply '{0}', diag='{1}'", ReplyToString(reply), resp.Message); + logger.LogError($"Got reply '{ReplyToString(reply)}', diag='{resp.Message}'"); #endif } diff --git a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj index 3e47a38b..67c0ab85 100644 --- a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj +++ b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj @@ -49,81 +49,41 @@ 10.0.15138.0 - + - + - + - + - + $(DefineConstants);HAS_TASK - + $(DefineConstants);HAS_LOGGER;HAS_BUFFERS;HAS_RUNTIMEINFORMATION - + $(DefineConstants);HAS_OLDLOGGER - + $(DefineConstants);HAS_INDEXRANGE - + $(DefineConstants);HAS_PROCESS;HAS_DRAWING;HAS_SERIALIZATION diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs new file mode 100644 index 00000000..59314683 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.Async.cs @@ -0,0 +1,284 @@ +#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.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +namespace AdvancedSharpAdbClient.DeviceCommands +{ + public static partial class DeviceExtensions + { + /// + /// Executes a shell command on the device. + /// + /// The to use when executing the command. + /// The device on which to run the command. + /// The command to execute. + /// Optionally, a that processes the command output. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task ExecuteShellCommandAsync(this IAdbClient client, DeviceData device, string command, IShellOutputReceiver receiver, CancellationToken cancellationToken = default) => + client.ExecuteRemoteCommandAsync(command, device, receiver, cancellationToken); + + /// + /// Gets the file statistics of a given file. + /// + /// The to use when executing the command. + /// The device on which to look for the file. + /// The path to the file. + /// A that can be used to cancel the task. + /// A which return a object that contains information about the file. + public static async Task StatAsync(this IAdbClient client, DeviceData device, string path, CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + return await service.StatAsync(path, cancellationToken); + } + + /// + /// Lists the contents of a directory on the device. + /// + /// The to use when executing the command. + /// The device on which to list the directory. + /// The path to the directory on the device. + /// A that can be used to cancel the task. + /// A which return for each child item of the directory, a object with information of the item. + public static async Task> List(this IAdbClient client, DeviceData device, string remotePath, CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + return await service.GetDirectoryListingAsync(remotePath, cancellationToken); + } + + /// + /// Pulls (downloads) a file from the remote device. + /// + /// The to use when executing the command. + /// The device on which to pull the file. + /// The path, on the device, of the file to pull. + /// A that will receive the contents of the file. + /// An optional handler for the event. + /// 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 file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + public static async Task PullAsync(this IAdbClient client, DeviceData device, + string remotePath, Stream stream, + EventHandler syncProgressEventHandler = null, + IProgress progress = null, CancellationToken cancellationToken = default ) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + if (syncProgressEventHandler != null) + { + service.SyncProgressChanged += syncProgressEventHandler; + } + + await service.PullAsync(remotePath, stream, progress, cancellationToken); + } + + /// + /// Pushes (uploads) a file to the remote device. + /// + /// The to use when executing the command. + /// The device on which to put the file. + /// The path, on the device, to which to push the file. + /// A that contains the contents of the file. + /// The permission octet that contains the permissions of the newly created file on the device. + /// The time at which the file was last modified. + /// An optional handler for the event. + /// 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 file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + public static async Task PushAsync(this IAdbClient client, DeviceData device, + string remotePath, Stream stream, int permissions, DateTimeOffset timestamp, + EventHandler syncProgressEventHandler = null, + IProgress progress = null, CancellationToken cancellationToken = default ) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + if (syncProgressEventHandler != null) + { + service.SyncProgressChanged += syncProgressEventHandler; + } + + await service.PushAsync(stream, remotePath, permissions, timestamp, progress, cancellationToken); + } + + /// + /// Gets the property of a device. + /// + /// The connection to the adb server. + /// The device for which to get the property. + /// The name of property which to get. + /// A that can be used to cancel the task. + /// A which return the value of the property on the device. + public static async Task GetPropertyAsync(this IAdbClient client, DeviceData device, string property, CancellationToken cancellationToken = default) + { + ConsoleOutputReceiver receiver = new(); + await client.ExecuteRemoteCommandAsync($"{GetPropReceiver.GetPropCommand} {property}", device, receiver, cancellationToken); + return receiver.ToString(); + } + + /// + /// Gets the properties of a device. + /// + /// The connection to the adb server. + /// The device for which to list the properties. + /// A that can be used to cancel the task. + /// A which return a dictionary containing the properties of the device, and their values. + public static async Task> GetPropertiesAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) + { + GetPropReceiver receiver = new(); + await client.ExecuteRemoteCommandAsync(GetPropReceiver.GetPropCommand, device, receiver, cancellationToken); + return receiver.Properties; + } + + /// + /// Gets the environment variables currently defined on a device. + /// + /// The connection to the adb server. + /// The device for which to list the environment variables. + /// A that can be used to cancel the task. + /// A which return the a dictionary containing the environment variables of the device, and their values. + public static async Task> GetEnvironmentVariablesAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) + { + EnvironmentVariablesReceiver receiver = new(); + await client.ExecuteRemoteCommandAsync(EnvironmentVariablesReceiver.PrintEnvCommand, device, receiver, cancellationToken); + return receiver.EnvironmentVariables; + } + + /// + /// 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. + /// 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); + return manager.UninstallPackageAsync(packageName, cancellationToken); + } + + /// + /// Requests the version information from the device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The name of the package from which to get the application version. + /// A which can be used to cancel the asynchronous operation. + /// 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); + return manager.GetVersionInfoAsync(packageName, cancellationToken); + } + + /// + /// Lists all processes running on the device. + /// + /// A connection to ADB. + /// The device on which to list the processes that are running. + /// A that can be used to cancel the task. + /// A which return the an that will iterate over all processes + /// that are currently running on the device. + public static async Task> ListProcessesAsync(this IAdbClient client, DeviceData device, CancellationToken cancellationToken = default) + { + // There are a couple of gotcha's when listing processes on an Android device. + // One way would be to run ps and parse the output. However, the output of + // ps different from Android version to Android version, is not delimited, nor + // entirely fixed length, and some of the fields can be empty, so it's almost impossible + // to parse correctly. + // + // The alternative is to directly read the values in /proc/[pid], pretty much like ps + // does (see https://android.googlesource.com/platform/system/core/+/master/toolbox/ps.c). + // + // The easiest way to do the directory listings would be to use the SyncService; unfortunately, + // the sync service doesn't work very well with /proc/ so we're back to using ls and taking it + // from there. + List processes = new(); + + // List all processes by doing ls /proc/. + // All subfolders which are completely numeric are PIDs + + // Android 7 and above ships with toybox (https://github.com/landley/toybox), which includes + // an updated ls which behaves slightly different. + // The -1 parameter is important to make sure each item gets its own line (it's an assumption we + // make when parsing output); on Android 7 and above we may see things like: + // 1 135 160 171 ioports timer_stats + // 10 13533 16056 172 irq tty + // 100 136 16066 173 kallsyms uid_cputime + // but unfortunately older versions do not handle the -1 parameter well. So we need to branch based + // on the API level. We do the branching on the device (inside a shell script) to avoid roundtrips. + // This if/then/else syntax was tested on Android 2.x, 4.x and 7 + ConsoleOutputReceiver receiver = new(); + await client.ExecuteShellCommandAsync(device, @"SDK=""$(/system/bin/getprop ro.build.version.sdk)"" +if [ $SDK -lt 24 ] +then + /system/bin/ls /proc/ +else + /system/bin/ls -1 /proc/ +fi".Replace("\r\n", "\n"), receiver, cancellationToken); + + Collection pids = new(); + + string output = receiver.ToString(); + using (StringReader reader = new(output)) + { + while (reader.Peek() > 0) + { + string line = +#if !NET35 + await reader.ReadLineAsync( +#if NET7_0_OR_GREATER + cancellationToken +#endif + ).ConfigureAwait(false); +#else + await Utilities.Run(reader.ReadLine, cancellationToken).ConfigureAwait(false); +#endif + + if (!line.All(char.IsDigit)) + { + continue; + } + + int pid = int.Parse(line); + + pids.Add(pid); + } + } + + // For each pid, we can get /proc/[pid]/stat, which contains the process information in a well-defined + // format - see http://man7.org/linux/man-pages/man5/proc.5.html. + // Doing cat on each file one by one takes too much time. Doing cat on all of them at the same time doesn't work + // either, because the command line would be too long. + // So we do it 25 processes at at time. + StringBuilder catBuilder = new(); + ProcessOutputReceiver processOutputReceiver = new(); + + _ = catBuilder.Append("cat "); + + for (int i = 0; i < pids.Count; i++) + { + _ = catBuilder.Append($"/proc/{pids[i]}/cmdline /proc/{pids[i]}/stat "); + + if (i > 0 && (i % 25 == 0 || i == pids.Count - 1)) + { + await client.ExecuteShellCommandAsync(device, catBuilder.ToString(), processOutputReceiver, cancellationToken); + _ = catBuilder.Clear(); + _ = catBuilder.Append("cat "); + } + } + + processOutputReceiver.Flush(); + + return processOutputReceiver.Processes; + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs index 7fdb18ab..e1d9f2e1 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs @@ -16,7 +16,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands /// Provides extension methods for the class, /// allowing you to run commands directory against a object. /// - public static class DeviceExtensions + public static partial class DeviceExtensions { /// /// Executes a shell command on the device. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/AndroidProcessState.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/AndroidProcessState.cs similarity index 100% rename from AdvancedSharpAdbClient/DeviceCommands/Models/AndroidProcessState.cs rename to AdvancedSharpAdbClient/DeviceCommands/Models/Enums/AndroidProcessState.cs diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PackageInstallProgressState.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PackageInstallProgressState.cs new file mode 100644 index 00000000..c3667fa7 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PackageInstallProgressState.cs @@ -0,0 +1,38 @@ +namespace AdvancedSharpAdbClient.DeviceCommands +{ + /// + /// Represents the state of the installation. + /// + public enum PackageInstallProgressState + { + /// + /// Uploading packages to the device. + /// + Uploading, + + /// + /// Create the session for the installation. + /// + CreateSession, + + /// + /// Write the package to link with session. + /// + WriteSession, + + /// + /// The install is in progress. + /// + Installing, + + /// + /// The installation has completed and cleanup actions are in progress. + /// + PostInstall, + + /// + /// The operation has completed. + /// + Finished, + } +} diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/PerProcessFlags.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PerProcessFlags.cs similarity index 100% rename from AdvancedSharpAdbClient/DeviceCommands/Models/PerProcessFlags.cs rename to AdvancedSharpAdbClient/DeviceCommands/Models/Enums/PerProcessFlags.cs diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/InstallProgressEventArgs.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/InstallProgressEventArgs.cs new file mode 100644 index 00000000..5cfe1e93 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/InstallProgressEventArgs.cs @@ -0,0 +1,63 @@ +using System; + +namespace AdvancedSharpAdbClient.DeviceCommands +{ + /// + /// Represents the state of the installation for . + /// + public class InstallProgressEventArgs : EventArgs + { + /// + /// State of the installation. + /// + public PackageInstallProgressState State { get; } + + /// + /// Number of packages which is finished operation. + /// Used only in , + /// and + /// state. + /// + public int PackageFinished { get; } + + /// + /// Number of packages required for this operation. + /// Used only in , + /// and + /// state. + /// + public int PackageRequired { get; } + + /// + /// Upload percentage completed. + /// Used only in state. + /// + public double UploadProgress { get; } + + /// + /// Initializes a new instance of the class. + /// + public InstallProgressEventArgs(PackageInstallProgressState state) => State = state; + + /// + /// Initializes a new instance of the class. + /// Which is used for state. + /// + public InstallProgressEventArgs(int packageUploaded, int packageRequired, double uploadProgress) : this(PackageInstallProgressState.Uploading) + { + PackageFinished = packageUploaded; + PackageRequired = packageRequired; + UploadProgress = uploadProgress; + } + + /// + /// Initializes a new instance of the class. + /// Which is used for and state. + /// + public InstallProgressEventArgs(int packageCleaned, int packageRequired, PackageInstallProgressState state) : this(state) + { + PackageFinished = packageCleaned; + PackageRequired = packageRequired; + } + } +} diff --git a/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs b/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs index 7c894608..c64b051a 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Models/VersionInfo.cs @@ -12,12 +12,12 @@ public class VersionInfo /// /// Gets or sets the version code of an Android application. /// - public int VersionCode { get; set; } + public int VersionCode { get; } /// /// Gets or sets the version name of an Android application. /// - public string VersionName { get; set; } + public string VersionName { get; } /// /// Initializes a new instance of the class. @@ -42,6 +42,6 @@ public void Deconstruct(out int versionCode, out string versionName) } /// - public override string ToString() => $"{VersionName} - {VersionCode}"; + public override string ToString() => $"{VersionName} ({VersionCode})"; } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs new file mode 100644 index 00000000..e5ae2183 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs @@ -0,0 +1,477 @@ +#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.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; + +namespace AdvancedSharpAdbClient.DeviceCommands +{ + public partial class PackageManager + { + /// + /// Refreshes the packages. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public Task RefreshPackagesAsync(CancellationToken cancellationToken = default) + { + ValidateDevice(); + + PackageManagerReceiver pmr = new(Device, this); + + return ThirdPartyOnly + ? client.ExecuteShellCommandAsync(Device, ListThirdPartyOnly, pmr, cancellationToken) + : client.ExecuteShellCommandAsync(Device, ListFull, pmr, cancellationToken); + } + + /// + /// Installs an Android application on device. + /// + /// The absolute file system path to file on local host to install. + /// if re-install of app should be performed; otherwise, . + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task InstallPackageAsync(string packageFilePath, bool reinstall, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + string remoteFilePath = await SyncPackageToDeviceAsync(packageFilePath, OnSyncProgressChanged, cancellationToken); + + await InstallRemotePackageAsync(remoteFilePath, reinstall, cancellationToken); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, 1, PackageInstallProgressState.PostInstall)); + await RemoveRemotePackageAsync(remoteFilePath, cancellationToken); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(1, 1, PackageInstallProgressState.PostInstall)); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + + void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(sender is true ? 1 : 0, 1, args.ProgressPercentage)); + } + + /// + /// Installs the application package that was pushed to a temporary location on the device. + /// + /// absolute file path to package file on device. + /// Set to if re-install of app should be performed. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task InstallRemotePackageAsync(string remoteFilePath, bool reinstall, CancellationToken cancellationToken = default) + { + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + + ValidateDevice(); + + InstallOutputReceiver receiver = new(); + string reinstallSwitch = reinstall ? "-r " : string.Empty; + + string cmd = $"pm install {reinstallSwitch}\"{remoteFilePath}\""; + await client.ExecuteShellCommandAsync(Device, cmd, receiver, cancellationToken); + + if (!string.IsNullOrEmpty(receiver.ErrorMessage)) + { + throw new PackageInstallationException(receiver.ErrorMessage); + } + } + + /// + /// 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. + /// Set to if re-install of app should be performed. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task InstallMultiplePackageAsync(string basePackageFilePath, IList splitPackageFilePaths, bool reinstall, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + void OnMainSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(sender is true ? 1 : 0, splitPackageFilePaths.Count + 1, args.ProgressPercentage / 2)); + + string baseRemoteFilePath = await SyncPackageToDeviceAsync(basePackageFilePath, OnMainSyncProgressChanged, cancellationToken); + + Dictionary progress = new(splitPackageFilePaths.Count); + void OnSplitSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) + { + int count = 1; + if (sender is string path) + { + progress[path] = args.ProgressPercentage; + } + else if (sender is true) + { + count++; + } + + double present = 0; + foreach (KeyValuePair info in progress) + { + present += (info.Value / splitPackageFilePaths.Count) / 2; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(count, splitPackageFilePaths.Count + 1, present)); + } + + List splitRemoteFilePaths = new(splitPackageFilePaths.Count); + IEnumerable tasks = splitPackageFilePaths.Select(async (x) => splitRemoteFilePaths.Add(await SyncPackageToDeviceAsync(x, OnSplitSyncProgressChanged, cancellationToken))); + foreach (Task task in tasks) + { + await task; + } + + await InstallMultipleRemotePackageAsync(baseRemoteFilePath, splitRemoteFilePaths, reinstall, cancellationToken); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.PostInstall)); + int count = 0; + tasks = splitRemoteFilePaths.Select(async (x) => + { + await RemoveRemotePackageAsync(x, cancellationToken); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.PostInstall)); + }); + foreach (Task task in tasks) + { + await task; + } + + await RemoveRemotePackageAsync(baseRemoteFilePath, cancellationToken); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.PostInstall)); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + } + + /// + /// 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. + /// Set to if re-install of app should be performed. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task InstallMultiplePackageAsync(IList splitPackageFilePaths, string packageName, bool reinstall, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + Dictionary progress = new(splitPackageFilePaths.Count); + void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) + { + int count = 1; + if (sender is string path) + { + progress[path] = args.ProgressPercentage; + } + else if (sender is true) + { + count++; + } + + double present = 0; + foreach (KeyValuePair info in progress) + { + present += (info.Value / splitPackageFilePaths.Count) / 2; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(count, splitPackageFilePaths.Count, present)); + } + + List splitRemoteFilePaths = new(splitPackageFilePaths.Count); + IEnumerable tasks = splitPackageFilePaths.Select(async (x) => splitRemoteFilePaths.Add(await SyncPackageToDeviceAsync(x, OnSyncProgressChanged, cancellationToken))); + foreach (Task task in tasks) + { + await task; + } + + await InstallMultipleRemotePackageAsync(splitRemoteFilePaths, packageName, reinstall, cancellationToken); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Count, PackageInstallProgressState.PostInstall)); + int count = 0; + tasks = splitRemoteFilePaths.Select(async (x) => + { + await RemoveRemotePackageAsync(x, cancellationToken); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Count, PackageInstallProgressState.PostInstall)); + }); + foreach (Task task in tasks) + { + await task; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + } + + /// + /// 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. + /// Set to if re-install of app should be performed. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task InstallMultipleRemotePackageAsync(string baseRemoteFilePath, IList splitRemoteFilePaths, bool reinstall, CancellationToken cancellationToken = default) + { + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + + ValidateDevice(); + + string session = await CreateInstallSessionAsync(reinstall, cancellationToken: cancellationToken); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.WriteSession)); + + await WriteInstallSessionAsync(session, "base", baseRemoteFilePath, cancellationToken); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(1, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.WriteSession)); + + int i = 0, count = 0; + IEnumerable tasks = splitRemoteFilePaths.Select(async (splitRemoteFilePath) => + { + try + { + await WriteInstallSessionAsync(session, $"splitapp{i++}", splitRemoteFilePath, cancellationToken); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.WriteSession)); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + }); + foreach (Task task in tasks) + { + await task; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + + InstallOutputReceiver receiver = new(); + await client.ExecuteShellCommandAsync(Device, $"pm install-commit {session}", receiver, cancellationToken); + + if (!string.IsNullOrEmpty(receiver.ErrorMessage)) + { + throw new PackageInstallationException(receiver.ErrorMessage); + } + } + + /// + /// 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. + /// Set to if re-install of app should be performed. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task InstallMultipleRemotePackageAsync(IList splitRemoteFilePaths, string packageName, bool reinstall, CancellationToken cancellationToken = default) + { + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + + ValidateDevice(); + + string session = await CreateInstallSessionAsync(reinstall, packageName, cancellationToken); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Count, PackageInstallProgressState.WriteSession)); + + int i = 0, count = 0; + IEnumerable tasks = splitRemoteFilePaths.Select(async (splitRemoteFilePath) => + { + try + { + await WriteInstallSessionAsync(session, $"splitapp{i++}", splitRemoteFilePath, cancellationToken); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Count, PackageInstallProgressState.WriteSession)); + } + catch (Exception ex) + { + Debug.WriteLine(ex.Message); + } + }); + foreach (Task task in tasks) + { + await task; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + + InstallOutputReceiver receiver = new(); + await client.ExecuteShellCommandAsync(Device, $"pm install-commit {session}", receiver, cancellationToken); + + if (!string.IsNullOrEmpty(receiver.ErrorMessage)) + { + throw new PackageInstallationException(receiver.ErrorMessage); + } + } + + /// + /// Uninstalls a package from the device. + /// + /// The name of the package to uninstall. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public async Task UninstallPackageAsync(string packageName, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + InstallOutputReceiver receiver = new(); + await client.ExecuteShellCommandAsync(Device, $"pm uninstall {packageName}", receiver, cancellationToken); + if (!string.IsNullOrEmpty(receiver.ErrorMessage)) + { + throw new PackageInstallationException(receiver.ErrorMessage); + } + } + + /// + /// 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. + /// A which return the of target application. + public async Task GetVersionInfoAsync(string packageName, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + VersionInfoReceiver receiver = new(); + await client.ExecuteShellCommandAsync(Device, $"dumpsys package {packageName}", receiver, cancellationToken); + return receiver.VersionInfo; + } + + /// + /// Pushes a file to device + /// + /// The absolute path to file on local host. + /// An optional parameter which, when specified, returns progress notifications. + /// A which can be used to cancel the asynchronous operation. + /// A which return the destination path on device for file. + /// If fatal error occurred when pushing file. + private async Task SyncPackageToDeviceAsync(string localFilePath, Action progress, CancellationToken cancellationToken = default) + { + progress(localFilePath, new SyncProgressChangedEventArgs(0, 0)); + + ValidateDevice(); + + try + { + string packageFileName = Path.GetFileName(localFilePath); + + // only root has access to /data/local/tmp/... not sure how adb does it then... + // workitem: 16823 + // workitem: 19711 + string remoteFilePath = LinuxPath.Combine(TempInstallationDirectory, packageFileName); + +#if HAS_LOGGER + logger.LogDebug(packageFileName, $"Uploading {packageFileName} onto device '{Device.Serial}'"); +#endif + + using (ISyncService sync = syncServiceFactory(client, Device)) + { + if (progress != null) + { + sync.SyncProgressChanged += (sender, e) => progress(localFilePath, e); + } + + using Stream stream = File.OpenRead(localFilePath); +#if HAS_LOGGER + logger.LogDebug($"Uploading file onto device '{Device.Serial}'"); +#endif + + // As C# can't use octal, the octal literal 666 (rw-Permission) is here converted to decimal (438) + await sync.PushAsync(stream, remoteFilePath, 438, File.GetLastWriteTime(localFilePath), null, cancellationToken); + } + + return remoteFilePath; + } +#if HAS_LOGGER + catch (IOException e) + { + logger.LogError(e, $"Unable to open sync connection! reason: {e.Message}"); +#else + catch (IOException) + { +#endif + throw; + } + finally + { + progress(true, new SyncProgressChangedEventArgs(0, 0)); + } + } + + /// + /// Remove a file from device. + /// + /// Path on device of file to remove. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + /// If file removal failed. + private async Task RemoveRemotePackageAsync(string remoteFilePath, CancellationToken cancellationToken = default) + { + // now we delete the app we synced + try + { + await client.ExecuteShellCommandAsync(Device, $"rm \"{remoteFilePath}\"", null, cancellationToken); + } +#if HAS_LOGGER + catch (IOException e) + { + logger.LogError(e, $"Failed to delete temporary package: {e.Message}"); +#else + catch (IOException) + { +#endif + throw; + } + } + + /// + /// Like "install", but starts an install session. + /// + /// Set to if re-install of app should be performed. + /// The absolute package name of the base app. + /// A which can be used to cancel the asynchronous operation. + /// A which return the session ID. + private async Task CreateInstallSessionAsync(bool reinstall, string packageName = null, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + InstallOutputReceiver receiver = new(); + string reinstallSwitch = reinstall ? " -r" : string.Empty; + string addon = packageName.IsNullOrWhiteSpace() ? string.Empty : $" -p {packageName}"; + + string cmd = $"pm install-create{reinstallSwitch}{addon}"; + await client.ExecuteShellCommandAsync(Device, cmd, receiver, cancellationToken); + + if (string.IsNullOrEmpty(receiver.SuccessMessage)) + { + throw new PackageInstallationException(receiver.ErrorMessage); + } + + string result = receiver.SuccessMessage; + int arr = result.IndexOf("]") - 1 - result.IndexOf("["); + string session = result.Substring(result.IndexOf("[") + 1, arr); + + return session; + } + + /// + /// Write an apk into the given install session. + /// + /// The session ID of the install session. + /// The name of the application. + /// The absolute file path to package file on device. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + private async Task WriteInstallSessionAsync(string session, string apkName, string path, CancellationToken cancellationToken = default) + { + ValidateDevice(); + + InstallOutputReceiver receiver = new(); + await client.ExecuteShellCommandAsync(Device, $"pm install-write {session} {apkName}.apk \"{path}\"", receiver, cancellationToken); + + if (!string.IsNullOrEmpty(receiver.ErrorMessage)) + { + throw new PackageInstallationException(receiver.ErrorMessage); + } + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs index c8bd9862..d0421c4a 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs @@ -14,7 +14,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands /// /// Allows you to get information about packages that are installed on a device. /// - public class PackageManager + public partial class PackageManager { /// /// The path to a temporary directory to use when pushing files to the device. @@ -50,17 +50,10 @@ public class PackageManager /// private readonly Func syncServiceFactory; - /// - /// Represents the method that will handle an event when the event provides double num. - /// - /// The source of the event. - /// An object that contains the double num. - public delegate void ProgressHandler(object sender, double e); - /// /// Occurs when there is a change in the status of the installing. /// - public event ProgressHandler InstallProgressChanged; + public event EventHandler InstallProgressChanged; #if !HAS_LOGGER #pragma warning disable CS1572 // XML 注释中有 param 标记,但是没有该名称的参数 @@ -150,15 +143,17 @@ public void InstallPackage(string packageFilePath, bool reinstall) ValidateDevice(); string remoteFilePath = SyncPackageToDevice(packageFilePath, OnSyncProgressChanged); + InstallRemotePackage(remoteFilePath, reinstall); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, 1, PackageInstallProgressState.PostInstall)); RemoveRemotePackage(remoteFilePath); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(1, 1, PackageInstallProgressState.PostInstall)); - InstallProgressChanged?.Invoke(this, 100); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Finished)); - void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) - { - InstallProgressChanged?.Invoke(this, args.ProgressPercentage * 0.9); - } + void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(sender is true ? 1 : 0, 1, args.ProgressPercentage)); } /// @@ -168,16 +163,16 @@ void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) /// Set to if re-install of app should be performed. public void InstallRemotePackage(string remoteFilePath, bool reinstall) { + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + ValidateDevice(); - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); string reinstallSwitch = reinstall ? "-r " : string.Empty; string cmd = $"pm install {reinstallSwitch}\"{remoteFilePath}\""; client.ExecuteShellCommand(Device, cmd, receiver); - InstallProgressChanged?.Invoke(this, 95); - if (!string.IsNullOrEmpty(receiver.ErrorMessage)) { throw new PackageInstallationException(receiver.ErrorMessage); @@ -194,31 +189,53 @@ public void InstallMultiplePackage(string basePackageFilePath, IList spl { ValidateDevice(); + void OnMainSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(sender is true ? 1 : 0, splitPackageFilePaths.Count + 1, args.ProgressPercentage / 2)); + string baseRemoteFilePath = SyncPackageToDevice(basePackageFilePath, OnMainSyncProgressChanged); - void OnMainSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => InstallProgressChanged?.Invoke(this, args.ProgressPercentage * 0.45); + Dictionary progress = new(splitPackageFilePaths.Count); + void OnSplitSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) + { + int count = 1; + if (sender is string path) + { + progress[path] = args.ProgressPercentage; + } + else if (sender is true) + { + count++; + } + + double present = 0; + foreach(KeyValuePair info in progress) + { + present += (info.Value / splitPackageFilePaths.Count) / 2; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(count, splitPackageFilePaths.Count + 1, present)); + } string[] splitRemoteFilePaths = new string[splitPackageFilePaths.Count]; for (int i = 0; i < splitPackageFilePaths.Count; i++) { - int percent = 45 + (45 * i / splitPackageFilePaths.Count); - splitRemoteFilePaths[i] = SyncPackageToDevice(splitPackageFilePaths[i], OnSplitSyncProgressChanged); - - void OnSplitSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => InstallProgressChanged?.Invoke(this, percent + (args.ProgressPercentage * 0.45 / splitPackageFilePaths.Count)); } InstallMultipleRemotePackage(baseRemoteFilePath, splitRemoteFilePaths, reinstall); - for (int i = 0; i < splitRemoteFilePaths.Length; i++) + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + int count = 0; + foreach (string splitRemoteFilePath in splitRemoteFilePaths) { - string splitRemoteFilePath = splitRemoteFilePaths[i]; RemoveRemotePackage(splitRemoteFilePath); - InstallProgressChanged?.Invoke(this, 95 + (5 * (i + 1) / (splitRemoteFilePaths.Length + 1))); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); } RemoveRemotePackage(baseRemoteFilePath); - InstallProgressChanged?.Invoke(this, 100); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -231,24 +248,45 @@ public void InstallMultiplePackage(IList splitPackageFilePaths, string p { ValidateDevice(); + Dictionary progress = new(splitPackageFilePaths.Count); + void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) + { + int count = 1; + if (sender is string path) + { + progress[path] = args.ProgressPercentage; + } + else if (sender is true) + { + count++; + } + + double present = 0; + foreach (KeyValuePair info in progress) + { + present += info.Value / splitPackageFilePaths.Count; + } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(count, splitPackageFilePaths.Count, present)); + } + string[] splitRemoteFilePaths = new string[splitPackageFilePaths.Count]; for (int i = 0; i < splitPackageFilePaths.Count; i++) { - int percent = 90 * i / splitPackageFilePaths.Count; - splitRemoteFilePaths[i] = SyncPackageToDevice(splitPackageFilePaths[i], OnSyncProgressChanged); - - void OnSyncProgressChanged(object sender, SyncProgressChangedEventArgs args) => InstallProgressChanged?.Invoke(this, percent + (args.ProgressPercentage * 0.9 / splitPackageFilePaths.Count)); } InstallMultipleRemotePackage(splitRemoteFilePaths, packageName, reinstall); - for (int i = 0; i < splitRemoteFilePaths.Length; i++) + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); + int count = 0; + foreach (string splitRemoteFilePath in splitRemoteFilePaths) { - string splitRemoteFilePath = splitRemoteFilePaths[i]; RemoveRemotePackage(splitRemoteFilePath); - InstallProgressChanged?.Invoke(this, 95 + (5 * (i + 1) / splitRemoteFilePaths.Length)); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); } + + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -259,15 +297,17 @@ public void InstallMultiplePackage(IList splitPackageFilePaths, string p /// Set to if re-install of app should be performed. public void InstallMultipleRemotePackage(string baseRemoteFilePath, IList splitRemoteFilePaths, bool reinstall) { + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + ValidateDevice(); string session = CreateInstallSession(reinstall); - InstallProgressChanged?.Invoke(this, 91); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.WriteSession)); WriteInstallSession(session, "base", baseRemoteFilePath); - InstallProgressChanged?.Invoke(this, 92); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(1, splitRemoteFilePaths.Count + 1, PackageInstallProgressState.WriteSession)); int i = 0; foreach (string splitRemoteFilePath in splitRemoteFilePaths) @@ -275,6 +315,7 @@ public void InstallMultipleRemotePackage(string baseRemoteFilePath, IListSet to if re-install of app should be performed. public void InstallMultipleRemotePackage(IList splitRemoteFilePaths, string packageName, bool reinstall) { + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + ValidateDevice(); string session = CreateInstallSession(reinstall, packageName); - InstallProgressChanged?.Invoke(this, 91); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(0, splitRemoteFilePaths.Count, PackageInstallProgressState.WriteSession)); int i = 0; foreach (string splitRemoteFilePath in splitRemoteFilePaths) @@ -315,6 +356,7 @@ public void InstallMultipleRemotePackage(IList splitRemoteFilePaths, str try { WriteInstallSession(session, $"splitapp{i++}", splitRemoteFilePath); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(i, splitRemoteFilePaths.Count, PackageInstallProgressState.WriteSession)); } catch (Exception ex) { @@ -322,13 +364,11 @@ public void InstallMultipleRemotePackage(IList splitRemoteFilePaths, str } } - InstallProgressChanged?.Invoke(this, 93); + InstallProgressChanged?.Invoke(this, new InstallProgressEventArgs(PackageInstallProgressState.Installing)); - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); client.ExecuteShellCommand(Device, $"pm install-commit {session}", receiver); - InstallProgressChanged?.Invoke(this, 95); - if (!string.IsNullOrEmpty(receiver.ErrorMessage)) { throw new PackageInstallationException(receiver.ErrorMessage); @@ -343,7 +383,7 @@ public void UninstallPackage(string packageName) { ValidateDevice(); - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); client.ExecuteShellCommand(Device, $"pm uninstall {packageName}", receiver); if (!string.IsNullOrEmpty(receiver.ErrorMessage)) { @@ -355,6 +395,7 @@ public void UninstallPackage(string packageName) /// Requests the version information from the device. /// /// The name of the package from which to get the application version. + /// The of target application. public VersionInfo GetVersionInfo(string packageName) { ValidateDevice(); @@ -381,6 +422,8 @@ private void ValidateDevice() /// If fatal error occurred when pushing file. private string SyncPackageToDevice(string localFilePath, Action progress) { + progress(localFilePath, new SyncProgressChangedEventArgs(0, 0)); + ValidateDevice(); try @@ -400,7 +443,7 @@ private string SyncPackageToDevice(string localFilePath, Action progress(sender, e); + sync.SyncProgressChanged += (sender, e) => progress(localFilePath, e); } using Stream stream = File.OpenRead(localFilePath); @@ -428,6 +471,10 @@ private string SyncPackageToDevice(string localFilePath, Action @@ -464,7 +511,7 @@ private string CreateInstallSession(bool reinstall, string packageName = null) { ValidateDevice(); - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); string reinstallSwitch = reinstall ? " -r" : string.Empty; string addon = packageName.IsNullOrWhiteSpace() ? string.Empty : $" -p {packageName}"; @@ -493,7 +540,7 @@ private void WriteInstallSession(string session, string apkName, string path) { ValidateDevice(); - InstallReceiver receiver = new(); + InstallOutputReceiver receiver = new(); client.ExecuteShellCommand(Device, $"pm install-write {session} {apkName}.apk \"{path}\"", receiver); if (!string.IsNullOrEmpty(receiver.ErrorMessage)) diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs index ac7fb985..4df27c4d 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/EnvironmentVariablesReceiver.cs @@ -54,14 +54,7 @@ protected override void ProcessNewLines(IEnumerable lines) if (label.Length > 0) { - if (EnvironmentVariables.ContainsKey(label)) - { - EnvironmentVariables[label] = value; - } - else - { - EnvironmentVariables.Add(label, value); - } + EnvironmentVariables[label] = value; } } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs index dc902f44..89435e1b 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/GetPropReceiver.cs @@ -53,7 +53,7 @@ protected override void ProcessNewLines(IEnumerable lines) if (label.Length > 0) { - Properties.Add(label, value); + Properties[label] = value; } } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs similarity index 87% rename from AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoReceiver.cs rename to AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs index c3a539d0..f7c0e213 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InfoOutputReceiver.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // @@ -10,7 +10,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands /// /// Processes command line output of a adb shell command. /// - public class InfoReceiver : MultiLineReceiver + public class InfoOutputReceiver : MultiLineReceiver { /// /// Gets or sets a dictionary with the extracted properties and their corresponding values. @@ -29,7 +29,7 @@ public class InfoReceiver : MultiLineReceiver /// /// The name of the property /// The received value - public object GetPropertyValue(string propertyName) => Properties.ContainsKey(propertyName) ? Properties[propertyName] : null; + public object GetPropertyValue(string propertyName) => Properties.TryGetValue(propertyName, out object property) ? property : null; /// /// Adds a new parser to this receiver. @@ -38,7 +38,7 @@ public class InfoReceiver : MultiLineReceiver /// /// The property corresponding with the parser. /// Function parsing one string and returning the property value if possible. - public void AddPropertyParser(string property, Func parser) => PropertyParsers.Add(property, parser); + 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. @@ -58,7 +58,7 @@ protected override void ProcessNewLines(IEnumerable lines) object propertyValue = parser.Value(line); if (propertyValue != null) { - Properties.Add(parser.Key, propertyValue); + Properties[parser.Key] = propertyValue; } } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs similarity index 94% rename from AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallReceiver.cs rename to AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs index 17313951..4598d0de 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/InstallOutputReceiver.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // @@ -10,7 +10,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands /// /// Processes output of the pm install command. /// - public partial class InstallReceiver : MultiLineReceiver + public partial class InstallOutputReceiver : MultiLineReceiver { /// /// The error message that indicates an unknown error occurred. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs index af59b8b4..9cc921dc 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/PackageManagerReceiver.cs @@ -63,7 +63,7 @@ protected override void ProcessNewLines(IEnumerable lines) if (separator == -1) { - PackageManager.Packages.Add(package, null); + PackageManager.Packages[package] = null; } else { @@ -74,7 +74,7 @@ protected override void ProcessNewLines(IEnumerable lines) string path = package.Substring(0, separator); string name = package.Substring(separator + 1); #endif - PackageManager.Packages.Add(name, path); + PackageManager.Packages[name] = path; } } } diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs index d0e5d761..136adb4a 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/ProcessOutputReceiver.cs @@ -11,7 +11,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands /// /// Parses the output of a cat /proc/[pid]/stat command. /// - internal class ProcessOutputReceiver : MultiLineReceiver + public class ProcessOutputReceiver : MultiLineReceiver { /// /// Gets a list of all processes that have been received. diff --git a/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs b/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs index 0e9a18d1..a4654f6f 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/Receivers/VersionInfoReceiver.cs @@ -10,7 +10,7 @@ namespace AdvancedSharpAdbClient.DeviceCommands /// /// Processes command line output of the dumpsys package command. /// - internal partial class VersionInfoReceiver : InfoReceiver + public partial class VersionInfoReceiver : InfoOutputReceiver { /// /// The name of the version code property. diff --git a/AdvancedSharpAdbClient/DeviceMonitor.Async.cs b/AdvancedSharpAdbClient/DeviceMonitor.Async.cs new file mode 100644 index 00000000..d37e6630 --- /dev/null +++ b/AdvancedSharpAdbClient/DeviceMonitor.Async.cs @@ -0,0 +1,147 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using AdvancedSharpAdbClient.Exceptions; +using System; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial class DeviceMonitor + { + /// + /// When the method is called, this + /// is used to block the method until the + /// has processed the first list of devices. + /// + private readonly ManualResetEvent firstDeviceListParsed = new(false); + + /// + /// A that can be used to cancel the . + /// + private readonly CancellationTokenSource monitorTaskCancellationTokenSource = new(); + + /// + /// The that monitors the and waits for device notifications. + /// + private Task monitorTask; + + /// + public async Task StartAsync(CancellationToken cancellationToken = default) + { + if (monitorTask == null) + { + _ = firstDeviceListParsed.Reset(); + + monitorTask = Utilities.Run(() => DeviceMonitorLoopAsync(monitorTaskCancellationTokenSource.Token), cancellationToken); + + // Wait for the worker thread to have read the first list of devices. + _ = await Utilities.Run(firstDeviceListParsed.WaitOne, cancellationToken); + } + } + + /// + /// 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. + private async Task DeviceMonitorLoopAsync(CancellationToken cancellationToken = default) + { + IsRunning = true; + + // Set up the connection to track the list of devices. + await InitializeSocketAsync(cancellationToken); + + do + { + try + { + string value = await Socket.ReadStringAsync(cancellationToken).ConfigureAwait(false); + ProcessIncomingDeviceData(value); + + firstDeviceListParsed.Set(); + } +#if HAS_LOGGER + catch (TaskCanceledException ex) +#else + catch (TaskCanceledException) +#endif + { + // We get a TaskCanceledException on Windows + if (cancellationToken.IsCancellationRequested) + { + // The DeviceMonitor is shutting down (disposing) and Dispose() + // has called cancellationToken.Cancel(). This exception is expected, + // so we can safely swallow it. + } + else + { + // The exception was unexpected, so log it & rethrow. +#if HAS_LOGGER + logger.LogError(ex, ex.Message); +#endif + throw; + } + } +#if HAS_LOGGER + catch (ObjectDisposedException ex) +#else + catch (ObjectDisposedException) +#endif + { + // ... but an ObjectDisposedException on .NET Core on Linux and macOS. + if (cancellationToken.IsCancellationRequested) + { + // The DeviceMonitor is shutting down (disposing) and Dispose() + // has called cancellationToken.Cancel(). This exception is expected, + // so we can safely swallow it. + } + else + { + // The exception was unexpected, so log it & rethrow. +#if HAS_LOGGER + logger.LogError(ex, ex.Message); +#endif + throw; + } + } + catch (AdbException adbException) + { + if (adbException.ConnectionReset) + { + // The adb server was killed, for whatever reason. Try to restart it and recover from this. + await AdbServer.Instance.RestartServerAsync(cancellationToken); + Socket.Reconnect(); + await InitializeSocketAsync(cancellationToken); + } + else + { + throw; + } + } +#if HAS_LOGGER + catch (Exception ex) + { + // The exception was unexpected, so log it & rethrow. + logger.LogError(ex, ex.Message); +#else + catch (Exception) + { +#endif + throw; + } + } + while (!cancellationToken.IsCancellationRequested); + } + + private async Task InitializeSocketAsync(CancellationToken cancellationToken) + { + // Set up the connection to track the list of devices. + await Socket.SendAdbRequestAsync("host:track-devices", cancellationToken); + _ = await Socket.ReadAdbResponseAsync(cancellationToken); + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/DeviceMonitor.cs b/AdvancedSharpAdbClient/DeviceMonitor.cs index 14bda54b..422f47ea 100644 --- a/AdvancedSharpAdbClient/DeviceMonitor.cs +++ b/AdvancedSharpAdbClient/DeviceMonitor.cs @@ -5,6 +5,7 @@ using AdvancedSharpAdbClient.Exceptions; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading; @@ -34,7 +35,7 @@ namespace AdvancedSharpAdbClient /// } /// /// - public class DeviceMonitor : IDeviceMonitor, IDisposable + public partial class DeviceMonitor : IDeviceMonitor, IDisposable { #if HAS_LOGGER /// @@ -48,24 +49,7 @@ public class DeviceMonitor : IDeviceMonitor, IDisposable /// private readonly List devices; -#if HAS_TASK - /// - /// When the method is called, this - /// is used to block the method until the - /// has processed the first list of devices. - /// - private readonly ManualResetEvent firstDeviceListParsed = new(false); - - /// - /// A that can be used to cancel the . - /// - private readonly CancellationTokenSource monitorTaskCancellationTokenSource = new(); - - /// - /// The that monitors the and waits for device notifications. - /// - private Task monitorTask; -#else +#if !HAS_TASK /// /// When the method is called, this /// is used to block the method until the @@ -110,23 +94,19 @@ public DeviceMonitor(IAdbSocket socket #endif /// - public event EventHandler DeviceChanged; + public event EventHandler DeviceChanged; /// - public event EventHandler DeviceConnected; + public event EventHandler DeviceNotified; /// - public event EventHandler DeviceDisconnected; + public event EventHandler DeviceConnected; /// - public -#if !NETFRAMEWORK || NET45_OR_GREATER - IReadOnlyCollection -#else - IEnumerable -#endif - Devices - { get; private set; } + public event EventHandler DeviceDisconnected; + + /// + public ReadOnlyCollection Devices { get; private set; } /// /// Gets the that represents the connection to the @@ -150,8 +130,7 @@ public void Start() monitorTask = Utilities.Run(() => DeviceMonitorLoopAsync(monitorTaskCancellationTokenSource.Token)); - // Wait for the worker thread to have read the first list - // of devices. + // Wait for the worker thread to have read the first list of devices. _ = firstDeviceListParsed.WaitOne(); } #else @@ -161,8 +140,7 @@ public void Start() monitorThread = new Thread(DeviceMonitorLoop); - // Wait for the worker thread to have read the first list - // of devices. + // Wait for the worker thread to have read the first list of devices. _ = firstDeviceListParsed.WaitOne(); } #endif @@ -234,116 +212,28 @@ public void Dispose() /// /// Raises the event. /// - /// The instance containing the event data. - protected void OnDeviceChanged(DeviceDataEventArgs e) => DeviceChanged?.Invoke(this, e); + /// The instance containing the event data. + protected void OnDeviceChanged(DeviceDataChangeEventArgs e) => DeviceChanged?.Invoke(this, e); /// - /// Raises the event. + /// Raises the event. /// - /// The instance containing the event data. - protected void OnDeviceConnected(DeviceDataEventArgs e) => DeviceConnected?.Invoke(this, e); + /// The instance containing the event data. + protected void OnDeviceNotified(DeviceDataNotifyEventArgs e) => DeviceNotified?.Invoke(this, e); /// - /// Raises the event. + /// Raises the event. /// - /// The instance containing the event data. - protected void OnDeviceDisconnected(DeviceDataEventArgs e) => DeviceDisconnected?.Invoke(this, e); + /// The instance containing the event data. + protected void OnDeviceConnected(DeviceDataConnectEventArgs e) => DeviceConnected?.Invoke(this, e); -#if HAS_TASK /// - /// Monitors the devices. This connects to the Debug Bridge + /// Raises the event. /// - /// A which can be used to cancel the asynchronous operation. - /// A which represents the asynchronous operation. - private async Task DeviceMonitorLoopAsync(CancellationToken cancellationToken = default) - { - IsRunning = true; - - // Set up the connection to track the list of devices. - InitializeSocket(); - - do - { - try - { - string value = await Socket.ReadStringAsync(cancellationToken).ConfigureAwait(false); - ProcessIncomingDeviceData(value); + /// The instance containing the event data. + protected void OnDeviceDisconnected(DeviceDataConnectEventArgs e) => DeviceDisconnected?.Invoke(this, e); - firstDeviceListParsed.Set(); - } -#if HAS_LOGGER - catch (TaskCanceledException ex) -#else - catch (TaskCanceledException) -#endif - { - // We get a TaskCanceledException on Windows - if (cancellationToken.IsCancellationRequested) - { - // The DeviceMonitor is shutting down (disposing) and Dispose() - // has called cancellationToken.Cancel(). This exception is expected, - // so we can safely swallow it. - } - else - { - // The exception was unexpected, so log it & rethrow. -#if HAS_LOGGER - logger.LogError(ex, ex.Message); -#endif - throw; - } - } -#if HAS_LOGGER - catch (ObjectDisposedException ex) -#else - catch (ObjectDisposedException) -#endif - { - // ... but an ObjectDisposedException on .NET Core on Linux and macOS. - if (cancellationToken.IsCancellationRequested) - { - // The DeviceMonitor is shutting down (disposing) and Dispose() - // has called cancellationToken.Cancel(). This exception is expected, - // so we can safely swallow it. - } - else - { - // The exception was unexpected, so log it & rethrow. -#if HAS_LOGGER - logger.LogError(ex, ex.Message); -#endif - throw; - } - } - catch (AdbException adbException) - { - if (adbException.ConnectionReset) - { - // The adb server was killed, for whatever reason. Try to restart it and recover from this. - AdbServer.Instance.RestartServer(); - Socket.Reconnect(); - InitializeSocket(); - } - else - { - throw; - } - } -#if HAS_LOGGER - catch (Exception ex) - { - // The exception was unexpected, so log it & rethrow. - logger.LogError(ex, ex.Message); -#else - catch (Exception) - { -#endif - throw; - } - } - while (!cancellationToken.IsCancellationRequested); - } -#else +#if !HAS_TASK /// /// Monitors the devices. This connects to the Debug Bridge /// @@ -381,7 +271,6 @@ private void DeviceMonitorLoop() while (!isMonitorThreadCancel); isMonitorThreadCancel = false; } -#endif private void InitializeSocket() { @@ -389,6 +278,7 @@ private void InitializeSocket() Socket.SendAdbRequest("host:track-devices"); _ = Socket.ReadAdbResponse(); } +#endif /// /// Processes the incoming device data. @@ -422,12 +312,13 @@ private void UpdateDevices(IEnumerable devices) if (existingDevice == null) { this.devices.Add(device); - OnDeviceConnected(new DeviceDataEventArgs(device)); + OnDeviceConnected(new DeviceDataConnectEventArgs(device, true)); } else if (existingDevice.State != device.State) { + DeviceState oldState = existingDevice.State; existingDevice.State = device.State; - OnDeviceChanged(new DeviceDataEventArgs(existingDevice)); + OnDeviceChanged(new DeviceDataChangeEventArgs(existingDevice, device.State, oldState)); } } @@ -435,7 +326,12 @@ private void UpdateDevices(IEnumerable devices) foreach (DeviceData device in Devices.Where(d => !devices.Any(e => e.Serial == d.Serial)).ToArray()) { this.devices.Remove(device); - OnDeviceDisconnected(new DeviceDataEventArgs(device)); + OnDeviceDisconnected(new DeviceDataConnectEventArgs(device, false)); + } + + if (devices.Any()) + { + OnDeviceNotified(new DeviceDataNotifyEventArgs(devices)); } } } diff --git a/AdvancedSharpAdbClient/Exceptions/ExceptionExtensions.cs b/AdvancedSharpAdbClient/Exceptions/ExceptionExtensions.cs index bf838235..4722c3b6 100644 --- a/AdvancedSharpAdbClient/Exceptions/ExceptionExtensions.cs +++ b/AdvancedSharpAdbClient/Exceptions/ExceptionExtensions.cs @@ -1,7 +1,7 @@ using System; -using System.Runtime.CompilerServices; -using System.Diagnostics.CodeAnalysis; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; namespace AdvancedSharpAdbClient.Exceptions { @@ -88,9 +88,7 @@ public static void ThrowIfNegative(int value, [CallerArgumentExpression(nameof(v /// The condition to evaluate. /// The object whose type's full name should be included in any resulting . /// The is . -#if NET6_0_OR_GREATER [StackTraceHidden] -#endif public static void ThrowIf( #if HAS_INDEXRANGE [DoesNotReturnIf(true)] diff --git a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs new file mode 100644 index 00000000..e3a9cf52 --- /dev/null +++ b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs @@ -0,0 +1,173 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using AdvancedSharpAdbClient.Exceptions; +using System; +using System.Net; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public static partial class AdbClientExtensions + { + /// + /// 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. + /// The local port to forward. + /// The remote port to forward to + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + /// If your requested to start forwarding to local port TCP:0, the port number of the TCP port + /// which has been opened. In all other cases, 0. + /// Failed to submit the forward command. Or Device rejected command: + resp.Message. + public static Task CreateForwardAsync(this IAdbClient client, DeviceData device, int localPort, int remotePort, CancellationToken cancellationToken = default) => + client.CreateForwardAsync(device, $"tcp:{localPort}", $"tcp:{remotePort}", true, cancellationToken); + + /// + /// 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. + /// The local port to forward. + /// The remote Unix socket. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + /// If your requested to start forwarding to local port TCP:0, the port number of the TCP port + /// which has been opened. In all other cases, 0. + /// The client failed to submit the forward command. + /// The device rejected command. The error message will include the error message provided by the device. + public static Task CreateForwardAsync(this IAdbClient client, DeviceData device, int localPort, string remoteSocket, CancellationToken cancellationToken = default) => + client.CreateForwardAsync(device, $"tcp:{localPort}", $"local:{remoteSocket}", true, cancellationToken); + + /// + /// Reboots the specified adb socket address. + /// + /// An instance of a class that implements the interface. + /// The device. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + 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. + /// + /// An instance of a class that implements the interface. + /// The IP address of the remote device. + /// The pairing code. + /// A which can be used to cancel the asynchronous operation. + /// The results from adb. + public static Task PairAsync(this IAdbClient client, IPAddress address, string code, CancellationToken cancellationToken = default) => + address == null + ? throw new ArgumentNullException(nameof(address)) + : client.PairAsync(new IPEndPoint(address, AdbClient.DefaultPort), code, cancellationToken); + + /// + /// 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. + /// The pairing code. + /// A which can be used to cancel the asynchronous operation. + /// The results from adb. + public static Task PairAsync(this IAdbClient client, IPEndPoint endpoint, string code, CancellationToken cancellationToken = default) => + endpoint == null + ? throw new ArgumentNullException(nameof(endpoint)) + : client.PairAsync(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port), code, cancellationToken); + + /// + /// 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. + /// The pairing code. + /// A which can be used to cancel the asynchronous operation. + /// The results from adb. + public static Task PairAsync(this IAdbClient client, string host, string code, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(host)) + { + throw new ArgumentNullException(nameof(host)); + } + + string[] values = host.Split(':'); + + return values.Length <= 0 + ? throw new ArgumentNullException(nameof(host)) + : client.PairAsync(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int port) ? port : AdbClient.DefaultPort), code, cancellationToken); + } + + /// + /// 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. + /// The port of the remote device. + /// The pairing code. + /// A which can be used to cancel the asynchronous operation. + /// The results from adb. + public static Task PairAsync(this IAdbClient client, string host, int port, string code, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(host)) + { + throw new ArgumentNullException(nameof(host)); + } + + string[] values = host.Split(':'); + + return values.Length <= 0 + ? throw new ArgumentNullException(nameof(host)) + : client.PairAsync(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int _port) ? _port : port), code, cancellationToken); + } + + /// + /// Connect to a device via TCP/IP. + /// + /// An instance of a class that implements the interface. + /// The IP address of the remote device. + /// A which can be used to cancel the asynchronous operation. + /// A which return the results from adb. + public static Task ConnectAsync(this IAdbClient client, IPAddress address, CancellationToken cancellationToken = default) => + address == null + ? throw new ArgumentNullException(nameof(address)) + : client.ConnectAsync(new IPEndPoint(address, AdbClient.DefaultPort), cancellationToken); + + /// + /// 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. + /// A which can be used to cancel the asynchronous operation. + /// A which return the results from adb. + public static Task ConnectAsync(this IAdbClient client, IPEndPoint endpoint, CancellationToken cancellationToken = default) => + endpoint == null + ? throw new ArgumentNullException(nameof(endpoint)) + : client.ConnectAsync(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port), cancellationToken); + + /// + /// Connect to a device via TCP/IP. + /// + /// An instance of a class that implements the interface. + /// The host address of the remote device. + /// The port of the remote device. + /// A which can be used to cancel the asynchronous operation. + /// A which return the results from adb. + public static Task ConnectAsync(this IAdbClient client, string host, int port = AdbClient.DefaultPort, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(host)) + { + throw new ArgumentNullException(nameof(host)); + } + + string[] values = host.Split(':'); + + return values.Length <= 0 + ? throw new ArgumentNullException(nameof(host)) + : client.ConnectAsync(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int _port) ? _port : port), cancellationToken); + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs index a57eafc3..ff149bac 100644 --- a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs @@ -5,14 +5,13 @@ using AdvancedSharpAdbClient.Exceptions; using System; using System.Net; -using System.Threading; namespace AdvancedSharpAdbClient { /// /// Provides extension methods for the interface. Provides overloads for commonly used functions. /// - public static class AdbClientExtensions + public static partial class AdbClientExtensions { /// /// Creates a port forwarding between a local and a remote port. @@ -21,6 +20,8 @@ public static class AdbClientExtensions /// The device to which to forward the connections. /// The local port to forward. /// The remote port to forward to + /// If your requested to start forwarding to local port TCP:0, the port number of the TCP port + /// which has been opened. In all other cases, 0. /// Failed to submit the forward command. Or Device rejected command: + resp.Message. public static int CreateForward(this IAdbClient client, DeviceData device, int localPort, int remotePort) => client.CreateForward(device, $"tcp:{localPort}", $"tcp:{remotePort}", true); @@ -32,6 +33,8 @@ public static int CreateForward(this IAdbClient client, DeviceData device, int l /// The device to which to forward the connections. /// The local port to forward. /// The remote Unix socket. + /// If your requested to start forwarding to local port TCP:0, the port number of the TCP port + /// which has been opened. In all other cases, 0. /// The client failed to submit the forward command. /// The device rejected command. The error message will include the error message provided by the device. public static int CreateForward(this IAdbClient client, DeviceData device, int localPort, string remoteSocket) => @@ -111,79 +114,6 @@ public static string Pair(this IAdbClient client, string host, int port, string : client.Pair(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int _port) ? _port : port), code); } -#if HAS_TASK - /// - /// 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. - /// The pairing code. - /// A which can be used to cancel the asynchronous operation. - /// The results from adb. - public static Task PairAsync(this IAdbClient client, IPAddress address, string code, CancellationToken cancellationToken = default) => - address == null - ? throw new ArgumentNullException(nameof(address)) - : client.PairAsync(new IPEndPoint(address, AdbClient.DefaultPort), code, cancellationToken); - - /// - /// 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. - /// The pairing code. - /// A which can be used to cancel the asynchronous operation. - /// The results from adb. - public static Task PairAsync(this IAdbClient client, IPEndPoint endpoint, string code, CancellationToken cancellationToken = default) => - endpoint == null - ? throw new ArgumentNullException(nameof(endpoint)) - : client.PairAsync(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port), code, cancellationToken); - - /// - /// 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. - /// The pairing code. - /// A which can be used to cancel the asynchronous operation. - /// The results from adb. - public static Task PairAsync(this IAdbClient client, string host, string code, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - string[] values = host.Split(':'); - - return values.Length <= 0 - ? throw new ArgumentNullException(nameof(host)) - : client.PairAsync(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int port) ? port : AdbClient.DefaultPort), code, cancellationToken); - } - - /// - /// 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. - /// The port of the remote device. - /// The pairing code. - /// A which can be used to cancel the asynchronous operation. - /// The results from adb. - public static Task PairAsync(this IAdbClient client, string host, int port, string code, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - string[] values = host.Split(':'); - - return values.Length <= 0 - ? throw new ArgumentNullException(nameof(host)) - : client.PairAsync(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int _port) ? _port : port), code, cancellationToken); - } -#endif - /// /// Connect to a device via TCP/IP. /// @@ -226,53 +156,5 @@ public static string Connect(this IAdbClient client, string host, int port = Adb ? throw new ArgumentNullException(nameof(host)) : client.Connect(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int _port) ? _port : port)); } - -#if HAS_TASK - /// - /// Connect to a device via TCP/IP. - /// - /// An instance of a class that implements the interface. - /// The IP address of the remote device. - /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. - public static Task ConnectAsync(this IAdbClient client, IPAddress address, CancellationToken cancellationToken = default) => - address == null - ? throw new ArgumentNullException(nameof(address)) - : client.ConnectAsync(new IPEndPoint(address, AdbClient.DefaultPort), cancellationToken); - - /// - /// 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. - /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. - public static Task ConnectAsync(this IAdbClient client, IPEndPoint endpoint, CancellationToken cancellationToken = default) => - endpoint == null - ? throw new ArgumentNullException(nameof(endpoint)) - : client.ConnectAsync(new DnsEndPoint(endpoint.Address.ToString(), endpoint.Port), cancellationToken); - - /// - /// Connect to a device via TCP/IP. - /// - /// An instance of a class that implements the interface. - /// The host address of the remote device. - /// The port of the remote device. - /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. - public static Task ConnectAsync(this IAdbClient client, string host, int port = AdbClient.DefaultPort, CancellationToken cancellationToken = default) - { - if (string.IsNullOrEmpty(host)) - { - throw new ArgumentNullException(nameof(host)); - } - - string[] values = host.Split(':'); - - return values.Length <= 0 - ? throw new ArgumentNullException(nameof(host)) - : client.ConnectAsync(new DnsEndPoint(values[0], values.Length > 1 && int.TryParse(values[1], out int _port) ? _port : port), cancellationToken); - } -#endif } } diff --git a/AdvancedSharpAdbClient/Extensions/AdbCommandLineClientExtensions.cs b/AdvancedSharpAdbClient/Extensions/AdbCommandLineClientExtensions.cs index f1f3b7c7..52d7a4da 100644 --- a/AdvancedSharpAdbClient/Extensions/AdbCommandLineClientExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/AdbCommandLineClientExtensions.cs @@ -3,7 +3,6 @@ // using AdvancedSharpAdbClient.Exceptions; -using System; using System.IO; namespace AdvancedSharpAdbClient diff --git a/AdvancedSharpAdbClient/Extensions/CallerArgumentExpressionAttribute.cs b/AdvancedSharpAdbClient/Extensions/Attributes/CallerArgumentExpressionAttribute.cs similarity index 100% rename from AdvancedSharpAdbClient/Extensions/CallerArgumentExpressionAttribute.cs rename to AdvancedSharpAdbClient/Extensions/Attributes/CallerArgumentExpressionAttribute.cs diff --git a/AdvancedSharpAdbClient/Extensions/ExcludeFromCodeCoverageAttribute.cs b/AdvancedSharpAdbClient/Extensions/Attributes/ExcludeFromCodeCoverageAttribute.cs similarity index 100% rename from AdvancedSharpAdbClient/Extensions/ExcludeFromCodeCoverageAttribute.cs rename to AdvancedSharpAdbClient/Extensions/Attributes/ExcludeFromCodeCoverageAttribute.cs diff --git a/AdvancedSharpAdbClient/Extensions/Attributes/StackTraceHiddenAttribute.cs b/AdvancedSharpAdbClient/Extensions/Attributes/StackTraceHiddenAttribute.cs new file mode 100644 index 00000000..4ebf69ce --- /dev/null +++ b/AdvancedSharpAdbClient/Extensions/Attributes/StackTraceHiddenAttribute.cs @@ -0,0 +1,20 @@ +#if !NET6_0_OR_GREATER +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics +{ + /// + /// Types and Methods attributed with StackTraceHidden will be omitted from the stack trace text shown in StackTrace.ToString() + /// and Exception.StackTrace + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Struct, Inherited = false)] + internal sealed class StackTraceHiddenAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + public StackTraceHiddenAttribute() { } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Extensions/CrossPlatformFunc.cs b/AdvancedSharpAdbClient/Extensions/CrossPlatformFunc.cs index ff8788c6..adbf0208 100644 --- a/AdvancedSharpAdbClient/Extensions/CrossPlatformFunc.cs +++ b/AdvancedSharpAdbClient/Extensions/CrossPlatformFunc.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Threading; namespace AdvancedSharpAdbClient { @@ -54,5 +55,80 @@ public static class CrossPlatformFunc throw new PlatformNotSupportedException(); #endif }; + +#if HAS_TASK +#if NETFRAMEWORK && !NET40_OR_GREATER + /// + /// Encapsulates a method that has five parameters and returns a value of the type specified by the parameter. + /// + /// The return value of the method that this delegate encapsulates. + public delegate TResult Func(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); +#endif + + /// + /// Runs process, invoking a specific command, and reads the standard output and standard error output. + /// + /// The return code of the process. + public static Func, List, CancellationToken, Task> RunProcessAsync { get; set; } = +#if HAS_PROCESS + async +#endif + (string filename, string command, List errorOutput, List standardOutput, CancellationToken cancellationToken) => + { +#if HAS_PROCESS + ProcessStartInfo psi = new(filename, command) + { + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true + }; + + using Process process = Process.Start(psi); +#if !NET35 + string standardErrorString = await process.StandardError.ReadToEndAsync( +#if NET7_0_OR_GREATER + cancellationToken +#endif + ); + string standardOutputString = await process.StandardOutput.ReadToEndAsync( +#if NET7_0_OR_GREATER + cancellationToken +#endif + ); +#else + string standardErrorString = await Utilities.Run(process.StandardError.ReadToEnd, cancellationToken).ConfigureAwait(false); + string standardOutputString = await Utilities.Run(process.StandardOutput.ReadToEnd, cancellationToken).ConfigureAwait(false); +#endif + + errorOutput?.AddRange(standardErrorString.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)); + + standardOutput?.AddRange(standardOutputString.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)); + +#if NET5_0_OR_GREATER + using (CancellationTokenSource completionSource = new(TimeSpan.FromMilliseconds(5000))) + { + await process.WaitForExitAsync(completionSource.Token); + if (!process.HasExited) + { + process.Kill(); + } + } +#else + // get the return code from the process + if (!process.WaitForExit(5000)) + { + process.Kill(); + } +#endif + return process.ExitCode; +#else + TaskCompletionSource source = new(); + source.SetException(new PlatformNotSupportedException()); + return source.Task; +#endif + }; +#endif } } diff --git a/AdvancedSharpAdbClient/Extensions/SocketExtensions.cs b/AdvancedSharpAdbClient/Extensions/SocketExtensions.cs index 33644bd9..ceaa0140 100644 --- a/AdvancedSharpAdbClient/Extensions/SocketExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/SocketExtensions.cs @@ -46,7 +46,7 @@ public static Task ReceiveAsync( TaskCompletionSource taskCompletionSource = new(socket); - socket.BeginReceive(buffer, offset, size, socketFlags, delegate (IAsyncResult iar) + socket.BeginReceive(buffer, offset, size, socketFlags, (iar) => { // this is the callback diff --git a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs index e047b89f..8c9490eb 100644 --- a/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs +++ b/AdvancedSharpAdbClient/Extensions/SyncCommandConverter.cs @@ -17,23 +17,18 @@ public static class SyncCommandConverter /// /// Maps the values to their string representations. /// - private static readonly Dictionary Values = new(); - - /// - /// Initializes static members of the class. - /// - static SyncCommandConverter() + private static readonly Dictionary Values = new() { - Values.Add(SyncCommand.DATA, "DATA"); - Values.Add(SyncCommand.DENT, "DENT"); - Values.Add(SyncCommand.DONE, "DONE"); - Values.Add(SyncCommand.FAIL, "FAIL"); - Values.Add(SyncCommand.LIST, "LIST"); - Values.Add(SyncCommand.OKAY, "OKAY"); - Values.Add(SyncCommand.RECV, "RECV"); - Values.Add(SyncCommand.SEND, "SEND"); - Values.Add(SyncCommand.STAT, "STAT"); - } + { SyncCommand.DATA, "DATA" }, + { SyncCommand.DENT, "DENT" }, + { SyncCommand.DONE, "DONE" }, + { SyncCommand.FAIL, "FAIL" }, + { SyncCommand.LIST, "LIST" }, + { SyncCommand.OKAY, "OKAY" }, + { SyncCommand.RECV, "RECV" }, + { SyncCommand.SEND, "SEND" }, + { SyncCommand.STAT, "STAT" } + }; /// /// Gets the byte array that represents the . diff --git a/AdvancedSharpAdbClient/Extensions/Utilities.cs b/AdvancedSharpAdbClient/Extensions/Utilities.cs index 4abb27b6..f980d935 100644 --- a/AdvancedSharpAdbClient/Extensions/Utilities.cs +++ b/AdvancedSharpAdbClient/Extensions/Utilities.cs @@ -257,7 +257,7 @@ is PlatformID.Unix /// Begins to asynchronously receive data from a connected . /// /// The Socket. - /// An array of type that is the storage location for the received data. + /// An array of type that is the storage location for the received data. /// The zero-based position in the parameter at which to store the received data. /// The number of bytes to receive. /// A bitwise combination of the values. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs index ed36d1a0..b95bd7a4 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs @@ -21,7 +21,7 @@ public partial interface IAdbClient /// Ask the ADB server for its internal version number. /// /// A which can be used to cancel the asynchronous operation. - /// An which return the ADB version number. + /// A which return the ADB version number. Task GetAdbVersionAsync(CancellationToken cancellationToken); /// @@ -37,7 +37,7 @@ public partial interface IAdbClient /// Gets the devices that are available for communication. /// /// A which can be used to cancel the asynchronous operation. - /// An which return the list of devices that are connected. + /// A which return the list of devices that are connected. Task> GetDevicesAsync(CancellationToken cancellationToken); /// @@ -207,7 +207,7 @@ public partial interface IAdbClient /// /// The device for which to list the existing forward connections. /// A which can be used to cancel the asynchronous operation. - /// An which return the entry for each existing forward connection. + /// A which return the entry for each existing forward connection. Task> ListForwardAsync(DeviceData device, CancellationToken cancellationToken); /// @@ -215,7 +215,7 @@ public partial interface IAdbClient /// /// The device for which to list the existing reverse foward connections. /// A which can be used to cancel the asynchronous operation. - /// An which return the entry for each existing reverse forward connection. + /// A which return the entry for each existing reverse forward connection. Task> ListReverseForwardAsync(DeviceData device, CancellationToken cancellationToken); /// @@ -249,6 +249,15 @@ public partial interface IAdbClient /// failed nudging Task GetFrameBufferAsync(DeviceData device, CancellationToken cancellationToken); + /// + /// Asynchronously runs the event log service on a device. + /// + /// The device on which to run the event log service. + /// A callback which will receive the event log messages as they are received. + /// Optionally, the names of the logs to receive. + /// A which represents the asynchronous operation. + Task RunLogServiceAsync(DeviceData device, Action messageSink, params LogId[] logNames); + /// /// Asynchronously runs the event log service on a device. /// @@ -256,7 +265,7 @@ public partial interface IAdbClient /// A callback which will receive the event log messages as they are received. /// A which can be used to cancel the event log service. Use this to stop reading from the event log. /// Optionally, the names of the logs to receive. - /// An which represents the asynchronous operation. + /// A which represents the asynchronous operation. Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames); /// @@ -274,7 +283,7 @@ public partial interface IAdbClient /// The DNS endpoint at which the adb server on the device is running. /// The pairing code. /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. + /// A which return the results from adb. Task PairAsync(DnsEndPoint endpoint, string code, CancellationToken cancellationToken); /// @@ -282,7 +291,7 @@ public partial interface IAdbClient /// /// The DNS endpoint at which the adb server on the device is running. /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. + /// A which return the results from adb. Task ConnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken); /// @@ -290,7 +299,7 @@ public partial interface IAdbClient /// /// The endpoint of the remote device to disconnect. /// A which can be used to cancel the asynchronous operation. - /// An which return the results from adb. + /// A which return the results from adb. Task DisconnectAsync(DnsEndPoint endpoint, CancellationToken cancellationToken); /// @@ -376,7 +385,7 @@ public partial interface IAdbClient /// The device on which to install the application. /// The package name of the baseAPK to install. /// The arguments to pass to adb install-create. - /// An which return the session ID + /// A which return the session ID Task InstallCreateAsync(DeviceData device, string packageName, params string[] arguments); /// @@ -386,7 +395,7 @@ public partial interface IAdbClient /// The package name of the baseAPK to install. /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install-create. - /// An which return the session ID + /// A which return the session ID Task InstallCreateAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments); /// @@ -414,7 +423,7 @@ public partial interface IAdbClient /// /// The device for which to get the list of features supported. /// A which can be used to cancel the asynchronous operation. - /// An which return the list of all features supported by the current device. + /// A which return the list of all features supported by the current device. Task> GetFeatureSetAsync(DeviceData device, CancellationToken cancellationToken); /// @@ -422,7 +431,7 @@ public partial interface IAdbClient /// /// The device for which to get the screen snapshot. /// A which can be used to cancel the asynchronous operation. - /// An which return a containing current hierarchy. + /// A which return a containing current hierarchy. /// Failed if start with ERROR or java.lang.Exception. Task DumpScreenStringAsync(DeviceData device, CancellationToken cancellationToken); @@ -431,7 +440,7 @@ public partial interface IAdbClient /// /// The device for which to get the screen snapshot. /// A which can be used to cancel the asynchronous operation. - /// An which return a containing current hierarchy. + /// A which return a containing current hierarchy. Task DumpScreenAsync(DeviceData device, CancellationToken cancellationToken); #if WINDOWS_UWP @@ -440,7 +449,7 @@ public partial interface IAdbClient /// /// The device for which to get the screen snapshot. /// A which can be used to cancel the asynchronous operation. - /// An which return a containing current hierarchy. + /// A which return a containing current hierarchy. Task DumpScreenWinRTAsync(DeviceData device, CancellationToken cancellationToken); #endif @@ -493,7 +502,7 @@ public partial interface IAdbClient /// The device on which to check. /// The package name of the app to check. /// A which can be used to cancel the asynchronous operation. - /// An which return the result. if the app is running in foreground; otherwise, . + /// A which return the result. if the app is running in foreground; otherwise, . Task IsCurrentAppAsync(DeviceData device, string packageName, CancellationToken cancellationToken); /// @@ -502,7 +511,7 @@ public partial interface IAdbClient /// The device on which to check. /// The package name of the app to check. /// A which can be used to cancel the asynchronous operation. - /// An which return the result. if the app is running in background; otherwise, . + /// A which return the result. if the app is running in background; otherwise, . Task IsAppRunningAsync(DeviceData device, string packageName, CancellationToken cancellationToken); /// @@ -511,7 +520,7 @@ public partial interface IAdbClient /// 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. - /// An which return the of the app. Foreground, stopped or running in background. + /// A which return the of the app. Foreground, stopped or running in background. Task GetAppStatusAsync(DeviceData device, string packageName, CancellationToken cancellationToken); /// @@ -521,7 +530,7 @@ public partial interface IAdbClient /// /// A which can be used to cancel the asynchronous operation. /// Only check once if . Or it will continue check until is . - /// A which represents the asynchronous operation. + /// A which return the of . Task FindElementAsync(DeviceData device, string xpath, CancellationToken cancellationToken); /// @@ -531,8 +540,20 @@ public partial interface IAdbClient /// 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 represents the asynchronous operation. - Task FindElementsAsync(DeviceData device, string xpath, CancellationToken cancellationToken); + /// 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. + IAsyncEnumerable FindAsyncElements(DeviceData device, string xpath, CancellationToken cancellationToken); +#endif /// /// Send key event to specific. You can see key events here https://developer.android.com/reference/android/view/KeyEvent. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs index 9f5100c3..b97f8688 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs @@ -3,6 +3,7 @@ // using AdvancedSharpAdbClient.Exceptions; +using AdvancedSharpAdbClient.Logs; using System; using System.Collections.Generic; using System.IO; @@ -273,6 +274,14 @@ public partial interface IAdbClient /// failed nudging Framebuffer GetFrameBuffer(DeviceData device); + /// + /// Runs the event log service on a device. + /// + /// The device on which to run the event log service. + /// 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 @@ -477,8 +486,8 @@ public partial interface IAdbClient /// The xpath of the elements. /// The timeout for waiting the elements. /// Only check once if or . - /// The of has got. - Element[] FindElements(DeviceData device, string xpath, TimeSpan timeout = default); + /// 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. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs new file mode 100644 index 00000000..c8495c71 --- /dev/null +++ b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.Async.cs @@ -0,0 +1,28 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial interface IAdbCommandLineClient + { + /// + /// 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. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + Task StartServerAsync(CancellationToken cancellationToken); + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs index 3419fb3c..a26e30a9 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbCommandLineClient.cs @@ -9,7 +9,7 @@ namespace AdvancedSharpAdbClient /// /// Provides a common interface for any class that provides access to the adb.exe executable. /// - public interface IAdbCommandLineClient + public partial interface IAdbCommandLineClient { /// /// Queries adb for its version number and checks it against . diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs new file mode 100644 index 00000000..8e40fa49 --- /dev/null +++ b/AdvancedSharpAdbClient/Interfaces/IAdbServer.Async.cs @@ -0,0 +1,91 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using AdvancedSharpAdbClient.Exceptions; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial interface IAdbServer + { + /// + /// 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 + /// is not running or is not up to date. + /// + /// + /// to restart the adb server if the version of the adb.exe + /// executable at is newer than the version that is currently + /// running; to keep a previous version of the server running. + /// + /// A which can be used to cancel the asynchronous operation. + /// + /// A which return + /// + /// + /// if the adb server was already + /// running and the version of the adb server was at least . + /// + /// + /// if the adb server + /// was already running, but the version was less than + /// or less than the version of the adb client at and the + /// flag was set. + /// + /// + /// + /// if the adb server was not running, + /// and the server was started. + /// + /// + /// + /// The server was not running, or an outdated version of the server was running, + /// and the parameter was not specified. + /// + Task StartServerAsync(string adbPath, bool restartServerIfNewer, CancellationToken cancellationToken); + + /// + /// 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. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + /// + /// You can only call this method if you have previously started the adb server via + /// and passed the full path to the adb server. + /// + Task RestartServerAsync(CancellationToken cancellationToken); + + /// + /// 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. + /// + /// + /// 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 use the path that was cached by + /// + /// + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + /// + /// You can only call this method if you have previously started the adb server via + /// and passed the full path to the adb server. + /// + Task RestartServerAsync(string adbPath, CancellationToken cancellationToken); + + /// + /// Gets the status of the adb server. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which return a object that describes the status of the adb server. + Task GetStatusAsync(CancellationToken cancellationToken); + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbServer.cs b/AdvancedSharpAdbClient/Interfaces/IAdbServer.cs index 937cf6bc..cbbc3912 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbServer.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbServer.cs @@ -9,7 +9,7 @@ namespace AdvancedSharpAdbClient /// /// Represents a common interface for any class that allows starting or stopping the Android Debug Bridge (adb) server/deamon. /// - public interface IAdbServer + public partial interface IAdbServer { /// /// Starts the adb server if it was not previously running. @@ -27,19 +27,19 @@ public interface IAdbServer /// /// /// - /// if the adb server was already - /// running and the version of the adb server was at least . + /// if the adb server was already + /// running and the version of the adb server was at least . /// /// - /// if the adb server - /// was already running, but the version was less than - /// or less than the version of the adb client at and the - /// flag was set. + /// if the adb server + /// was already running, but the version was less than + /// or less than the version of the adb client at and the + /// flag was set. /// /// /// - /// if the adb server was not running, - /// and the server was started. + /// if the adb server was not running, + /// and the server was started. /// /// /// @@ -53,11 +53,16 @@ public interface IAdbServer /// you receive an with the flag /// set to - a clear indicating the ADB server died. /// + /// + /// 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 use the path that was cached by + /// + /// /// /// You can only call this method if you have previously started the adb server via /// and passed the full path to the adb server. /// - void RestartServer(); + StartServerResult RestartServer(string adbPath = null); /// /// Gets the status of the adb server. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs index a3d01041..b406758e 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs @@ -72,7 +72,7 @@ public partial interface IAdbSocket /// The buffer to store the read data into. /// A that can be used to cancel the task. /// A that represents the asynchronous operation. - Task ReadAsync(byte[] data, CancellationToken cancellationToken); + Task ReadAsync(byte[] data, CancellationToken cancellationToken); /// /// Receives data from a into a receive buffer. diff --git a/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs b/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs new file mode 100644 index 00000000..2218f59e --- /dev/null +++ b/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.Async.cs @@ -0,0 +1,20 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial interface IDeviceMonitor + { + /// + /// Starts the monitoring. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + Task StartAsync(CancellationToken cancellationToken); + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.cs b/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.cs index 75c2c3e4..e166fd32 100644 --- a/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.cs +++ b/AdvancedSharpAdbClient/Interfaces/IDeviceMonitor.cs @@ -3,40 +3,39 @@ // using System; -using System.Collections.Generic; +using System.Collections.ObjectModel; namespace AdvancedSharpAdbClient { /// /// Provides a common interface for any class that allows you to monitor the list of devices that are currently connected to the adb server. /// - public interface IDeviceMonitor : IDisposable + public partial interface IDeviceMonitor : IDisposable { /// /// Occurs when the status of one of the connected devices has changed. /// - event EventHandler DeviceChanged; + event EventHandler DeviceChanged; + + /// + /// Occurs when received a list of device from the Android Debug Bridge. + /// + event EventHandler DeviceNotified; /// /// Occurs when a device has connected to the Android Debug Bridge. /// - event EventHandler DeviceConnected; + event EventHandler DeviceConnected; /// /// Occurs when a device has disconnected from the Android Debug Bridge. /// - event EventHandler DeviceDisconnected; + event EventHandler DeviceDisconnected; /// /// Gets the devices that are currently connected to the Android Debug Bridge. /// -#if !NETFRAMEWORK || NET45_OR_GREATER - IReadOnlyCollection -#else - IEnumerable -#endif - Devices - { get; } + ReadOnlyCollection Devices { get; } /// /// Starts the monitoring. diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index cf801631..f6f8c18e 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -39,7 +39,7 @@ public partial interface ISyncService /// /// The path of the file on the device. /// A that can be used to cancel the task. - /// An which return a object that contains information about the file. + /// A which return a object that contains information about the file. Task StatAsync(string remotePath, CancellationToken cancellationToken); /// @@ -47,7 +47,7 @@ public partial interface ISyncService /// /// The path to the directory on the device. /// A that can be used to cancel the task. - /// An which return for each child item of the directory, a object with information of the item. + /// A which return for each child item of the directory, a object with information of the item. Task> GetDirectoryListingAsync(string remotePath, CancellationToken cancellationToken); #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER diff --git a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs new file mode 100644 index 00000000..53652a36 --- /dev/null +++ b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs @@ -0,0 +1,40 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System.Net.Sockets; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial interface ITcpSocket + { + /// + /// Asynchronously sends the specified number of bytes of data to a connected + /// , starting at the specified , + /// and using the specified . + /// + /// An array of type Byte that contains the data to be sent. + /// The position in the data buffer at which to begin sending data. + /// The number of bytes to send. + /// A bitwise combination of the SocketFlags values. + /// A which can be used to cancel the asynchronous task. + /// The number of bytes sent to the Socket. + public Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); + + /// + /// 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. + /// The location in buffer to store the received data. + /// The number of bytes to receive. + /// A bitwise combination of the SocketFlags values. + /// A which can be used to cancel the asynchronous task. + /// Cancelling the task will also close the socket. + /// The number of bytes received. + Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.cs b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.cs index ee25836c..eac68481 100644 --- a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.cs +++ b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.cs @@ -6,7 +6,6 @@ using System.IO; using System.Net; using System.Net.Sockets; -using System.Threading; namespace AdvancedSharpAdbClient { @@ -15,7 +14,7 @@ namespace AdvancedSharpAdbClient /// class. The main purpose of this interface is to enable mocking of the /// in unit test scenarios. /// - public interface ITcpSocket : IDisposable + public partial interface ITcpSocket : IDisposable { /// /// Gets a value indicating whether a is connected to a remote host as of the last @@ -52,21 +51,6 @@ public interface ITcpSocket : IDisposable /// The number of bytes sent to the Socket. int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags); -#if HAS_TASK - /// - /// Asynchronously sends the specified number of bytes of data to a connected - /// , starting at the specified , - /// and using the specified . - /// - /// An array of type Byte that contains the data to be sent. - /// The position in the data buffer at which to begin sending data. - /// The number of bytes to send. - /// A bitwise combination of the SocketFlags values. - /// A which can be used to cancel the asynchronous task. - /// The number of bytes sent to the Socket. - public Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); -#endif - /// /// Receives the specified number of bytes from a bound into the specified offset position of the /// receive buffer, using the specified SocketFlags. @@ -77,21 +61,6 @@ public interface ITcpSocket : IDisposable /// The number of bytes received. int Receive(byte[] buffer, int size, SocketFlags socketFlags); -#if HAS_TASK - /// - /// 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. - /// The location in buffer to store the received data. - /// The number of bytes to receive. - /// A bitwise combination of the SocketFlags values. - /// A which can be used to cancel the asynchronous task. - /// Cancelling the task will also close the socket. - /// The number of bytes received. - Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); -#endif - /// /// Gets the underlying . /// diff --git a/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs b/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs index 303570a0..9cf930ab 100644 --- a/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs +++ b/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs @@ -51,6 +51,6 @@ public override string ToString() => /// The value to convert. /// A that represents in the system log. private static char FormatPriority(Priority value) => - PriorityFormatters == null || !PriorityFormatters.ContainsKey(value) ? '?' : PriorityFormatters[value]; + PriorityFormatters?.TryGetValue(value, out var result) == true ? result : '?'; } } diff --git a/AdvancedSharpAdbClient/Logs/LogEntry.cs b/AdvancedSharpAdbClient/Logs/LogEntry.cs index aa53ac4c..6a74fc21 100644 --- a/AdvancedSharpAdbClient/Logs/LogEntry.cs +++ b/AdvancedSharpAdbClient/Logs/LogEntry.cs @@ -7,8 +7,8 @@ namespace AdvancedSharpAdbClient.Logs { /// - /// The userspace structure for version 1 of the logger_entry ABI. - /// This structure is returned to userspace by the kernel logger + /// The user space structure for version 1 of the logger_entry ABI. + /// This structure is returned to user space by the kernel logger /// driver unless an upgrade to a newer ABI version is requested. /// /// diff --git a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs new file mode 100644 index 00000000..9a7d1967 --- /dev/null +++ b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs @@ -0,0 +1,221 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using AdvancedSharpAdbClient.Exceptions; +using System; +using System.IO; +using System.Text; +using System.Threading; + +namespace AdvancedSharpAdbClient.Logs +{ + public partial class LogReader + { + /// + /// Reads the next from the stream. + /// + /// A which can be used to cancel the asynchronous operation. + /// A which return a new object. + public async Task ReadEntryAsync(CancellationToken cancellationToken = default) + { + // Read the log data in binary format. This format is defined at + // https://android.googlesource.com/platform/system/core/+/master/include/log/logger.h + // https://android.googlesource.com/platform/system/core/+/67d7eaf/include/log/logger.h + ushort? payloadLengthValue = await ReadUInt16Async(cancellationToken).ConfigureAwait(false); + ushort? headerSizeValue = payloadLengthValue == null ? null : await ReadUInt16Async(cancellationToken).ConfigureAwait(false); + int? pidValue = headerSizeValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); + int? tidValue = pidValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); + int? secValue = tidValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); + int? nsecValue = secValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); + + if (nsecValue == null) + { + return null; + } + + ushort payloadLength = payloadLengthValue.Value; + ushort headerSize = headerSizeValue.Value; + int pid = pidValue.Value; + int tid = tidValue.Value; + int sec = secValue.Value; + int nsec = nsecValue.Value; + + // If the headerSize is not 0, we have on of the logger_entry_v* objects. + // In all cases, it appears that they always start with a two uint16's giving the + // header size and payload length. + // For both objects, the size should be 0x18 + uint id = 0; + uint uid = 0; + + if (headerSize != 0) + { + if (headerSize >= 0x18) + { + uint? idValue = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + + if (idValue == null) + { + return null; + } + + id = idValue.Value; + } + + if (headerSize >= 0x1c) + { + uint? uidValue = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + + if (uidValue == null) + { + return null; + } + + uid = uidValue.Value; + } + + if (headerSize >= 0x20) + { + // Not sure what this is. + _ = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + } + + if (headerSize > 0x20) + { + throw new AdbException($"An error occurred while reading data from the ADB stream. Although the header size was expected to be 0x18, a header size of 0x{headerSize:X} was sent by the device"); + } + } + + byte[] data = await ReadBytesSafeAsync(payloadLength, cancellationToken).ConfigureAwait(false); + + if (data == null) + { + return null; + } + + DateTimeOffset timestamp = Utilities.FromUnixTimeSeconds(sec); + + switch ((LogId)id) + { + case LogId.Crash + or LogId.Kernel + or LogId.Main + or LogId.Radio + or LogId.System: + { + // format: \0\0 + byte priority = data[0]; + + // Find the first \0 byte in the array. This is the separator + // between the tag and the actual message + int tagEnd = 1; + + while (data[tagEnd] != '\0' && tagEnd < data.Length) + { + tagEnd++; + } + + // Message should be null terminated, so remove the last entry, too (-2 instead of -1) + string tag = Encoding.ASCII.GetString(data, 1, tagEnd - 1); + string message = Encoding.ASCII.GetString(data, tagEnd + 1, data.Length - tagEnd - 2); + + return new AndroidLogEntry + { + Data = data, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + Id = id, + Priority = (Priority)priority, + Message = message, + Tag = tag + }; + } + + case LogId.Events: + { + // https://android.googlesource.com/platform/system/core.git/+/master/liblog/logprint.c#547 + EventLogEntry entry = new() + { + Data = data, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + Id = id + }; + + // Use a stream on the data buffer. This will make sure that, + // if anything goes wrong parsing the data, we never go past + // the message boundary itself. + using (MemoryStream dataStream = new(data)) + { + using BinaryReader reader = new(dataStream); + int priority = reader.ReadInt32(); + + while (dataStream.Position < dataStream.Length) + { + ReadLogEntry(reader, entry.Values); + } + } + + return entry; + } + + default: + return new LogEntry + { + Data = data, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + Id = id + }; + } + } + private async Task ReadUInt16Async(CancellationToken cancellationToken = default) + { + byte[] data = await ReadBytesSafeAsync(2, cancellationToken).ConfigureAwait(false); + + return data == null ? null : BitConverter.ToUInt16(data, 0); + } + + private async Task ReadUInt32Async(CancellationToken cancellationToken = default) + { + byte[] data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); + + return data == null ? null : BitConverter.ToUInt32(data, 0); + } + + private async Task ReadInt32Async(CancellationToken cancellationToken = default) + { + byte[] data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); + + return data == null ? null : BitConverter.ToInt32(data, 0); + } + + private async Task ReadBytesSafeAsync(int count, CancellationToken cancellationToken = default) + { + int totalRead = 0; + int read = 0; + + byte[] data = new byte[count]; + + while ((read = +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + await stream.ReadAsync(data.AsMemory(totalRead, count - totalRead), cancellationToken).ConfigureAwait(false) +#elif !NET35 + await stream.ReadAsync(data, totalRead, count - totalRead, cancellationToken).ConfigureAwait(false) +#else + await Utilities.Run(() => stream.Read(data, totalRead, count - totalRead)).ConfigureAwait(false) +#endif + ) > 0) + { + totalRead += read; + } + + return totalRead < count ? null : data; + } + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Logs/LogReader.cs b/AdvancedSharpAdbClient/Logs/LogReader.cs index 6ab500f6..5d5692e9 100644 --- a/AdvancedSharpAdbClient/Logs/LogReader.cs +++ b/AdvancedSharpAdbClient/Logs/LogReader.cs @@ -7,14 +7,13 @@ using System.Collections.ObjectModel; using System.IO; using System.Text; -using System.Threading; namespace AdvancedSharpAdbClient.Logs { /// /// Processes Android log files in binary format. You usually get the binary format by running logcat -B. /// - public class LogReader + public partial class LogReader { private readonly Stream stream; @@ -24,24 +23,21 @@ public class LogReader /// A that contains the logcat data. public LogReader(Stream stream) => this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); -#if HAS_TASK /// /// Reads the next from the stream. /// /// A new object. - public async Task ReadEntry(CancellationToken cancellationToken = default) + public LogEntry ReadEntry() { - //LogEntry value = new(); - // Read the log data in binary format. This format is defined at // https://android.googlesource.com/platform/system/core/+/master/include/log/logger.h // https://android.googlesource.com/platform/system/core/+/67d7eaf/include/log/logger.h - ushort? payloadLengthValue = await ReadUInt16Async(cancellationToken).ConfigureAwait(false); - ushort? headerSizeValue = payloadLengthValue == null ? null : await ReadUInt16Async(cancellationToken).ConfigureAwait(false); - int? pidValue = headerSizeValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); - int? tidValue = pidValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); - int? secValue = tidValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); - int? nsecValue = secValue == null ? null : await ReadInt32Async(cancellationToken).ConfigureAwait(false); + ushort? payloadLengthValue = ReadUInt16(); + ushort? headerSizeValue = payloadLengthValue == null ? null : ReadUInt16(); + int? pidValue = headerSizeValue == null ? null : ReadInt32(); + int? tidValue = pidValue == null ? null : ReadInt32(); + int? secValue = tidValue == null ? null : ReadInt32(); + int? nsecValue = secValue == null ? null : ReadInt32(); if (nsecValue == null) { @@ -53,20 +49,20 @@ public async Task ReadEntry(CancellationToken cancellationToken = defa int pid = pidValue.Value; int tid = tidValue.Value; int sec = secValue.Value; - //int nsec = nsecValue.Value; + int nsec = nsecValue.Value; // If the headerSize is not 0, we have on of the logger_entry_v* objects. // In all cases, it appears that they always start with a two uint16's giving the // header size and payload length. // For both objects, the size should be 0x18 uint id = 0; - //uint uid = 0; + uint uid = 0; if (headerSize != 0) { if (headerSize >= 0x18) { - uint? idValue = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + uint? idValue = ReadUInt32(); if (idValue == null) { @@ -76,22 +72,22 @@ public async Task ReadEntry(CancellationToken cancellationToken = defa id = idValue.Value; } - //if (headerSize >= 0x1c) - //{ - // uint? uidValue = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + if (headerSize >= 0x1c) + { + uint? uidValue = ReadUInt32(); - // if (uidValue == null) - // { - // return null; - // } + if (uidValue == null) + { + return null; + } - // uid = uidValue.Value; - //} + uid = uidValue.Value; + } if (headerSize >= 0x20) { // Not sure what this is. - _ = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + _ = ReadUInt32(); } if (headerSize > 0x20) @@ -100,7 +96,7 @@ public async Task ReadEntry(CancellationToken cancellationToken = defa } } - byte[] data = await ReadBytesSafeAsync(payloadLength, cancellationToken).ConfigureAwait(false); + byte[] data = ReadBytesSafe(payloadLength); if (data == null) { @@ -133,7 +129,7 @@ or LogId.Radio string tag = Encoding.ASCII.GetString(data, 1, tagEnd - 1); string message = Encoding.ASCII.GetString(data, tagEnd + 1, data.Length - tagEnd - 2); - return new AndroidLogEntry() + return new AndroidLogEntry { Data = data, ProcessId = pid, @@ -176,7 +172,7 @@ or LogId.Radio } default: - return new LogEntry() + return new LogEntry { Data = data, ProcessId = pid, @@ -186,7 +182,6 @@ or LogId.Radio }; } } -#endif private void ReadLogEntry(BinaryReader reader, Collection parent) { @@ -227,51 +222,39 @@ private void ReadLogEntry(BinaryReader reader, Collection parent) break; } } - -#if HAS_TASK - private async Task ReadUInt16Async(CancellationToken cancellationToken = default) + private ushort? ReadUInt16() { - byte[] data = await ReadBytesSafeAsync(2, cancellationToken).ConfigureAwait(false); + byte[] data = ReadBytesSafe(2); return data == null ? null : BitConverter.ToUInt16(data, 0); } - private async Task ReadUInt32Async(CancellationToken cancellationToken = default) + private uint? ReadUInt32() { - byte[] data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); + byte[] data = ReadBytesSafe(4); return data == null ? null : BitConverter.ToUInt32(data, 0); } - private async Task ReadInt32Async(CancellationToken cancellationToken = default) + private int? ReadInt32() { - byte[] data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); + byte[] data = ReadBytesSafe(4); return data == null ? null : BitConverter.ToInt32(data, 0); } - private async Task ReadBytesSafeAsync(int count, CancellationToken cancellationToken = default) + private byte[] ReadBytesSafe(int count) { int totalRead = 0; - int read = 0; - byte[] data = new byte[count]; - while ((read = -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - await stream.ReadAsync(data.AsMemory(totalRead, count - totalRead), cancellationToken).ConfigureAwait(false) -#elif !NET35 - await stream.ReadAsync(data, totalRead, count - totalRead, cancellationToken).ConfigureAwait(false) -#else - await Utilities.Run(() => stream.Read(data, totalRead, count - totalRead)).ConfigureAwait(false) -#endif - ) > 0) + int read; + while ((read = stream.Read(data, totalRead, count - totalRead)) > 0) { totalRead += read; } return totalRead < count ? null : data; } -#endif } } diff --git a/AdvancedSharpAdbClient/Logs/ShellStream.cs b/AdvancedSharpAdbClient/Logs/ShellStream.cs index b744e1ba..1270b9b9 100644 --- a/AdvancedSharpAdbClient/Logs/ShellStream.cs +++ b/AdvancedSharpAdbClient/Logs/ShellStream.cs @@ -5,7 +5,6 @@ using AdvancedSharpAdbClient.Exceptions; using System; using System.IO; -using System.Runtime.InteropServices; using System.Threading; namespace AdvancedSharpAdbClient.Logs diff --git a/AdvancedSharpAdbClient/Models/AdbServerStatus.cs b/AdvancedSharpAdbClient/Models/AdbServerStatus.cs index 066a8ae5..1caa6bf7 100644 --- a/AdvancedSharpAdbClient/Models/AdbServerStatus.cs +++ b/AdvancedSharpAdbClient/Models/AdbServerStatus.cs @@ -12,14 +12,36 @@ namespace AdvancedSharpAdbClient public struct AdbServerStatus { /// - /// Gets or sets a value indicating whether the server is currently running. + /// Gets a value indicating whether the server is currently running. /// - public bool IsRunning { get; set; } + public bool IsRunning { get; internal set; } /// - /// Gets or sets, when the server is running, the version of the server that is running. + /// Gets the version of the server when it is running. /// - public Version Version { get; set; } + public Version Version { get; internal set; } + + /// + /// Initializes a new instance of the struct. + /// + /// The value indicating whether the server is currently running. + /// The version of the server when it is running. + public AdbServerStatus(bool isRunning, Version version) + { + IsRunning = isRunning; + Version = version; + } + + /// + /// Deconstruct the struct. + /// + /// The value indicating whether the server is currently running. + /// The version of the server when it is running. + public readonly void Deconstruct(out bool isRunning, out Version version) + { + isRunning = IsRunning; + version = Version; + } /// /// Gets a that represents the current object. diff --git a/AdvancedSharpAdbClient/Models/Area.cs b/AdvancedSharpAdbClient/Models/Area.cs index c193b56b..7c529eba 100644 --- a/AdvancedSharpAdbClient/Models/Area.cs +++ b/AdvancedSharpAdbClient/Models/Area.cs @@ -122,6 +122,7 @@ public Area(Windows.Foundation.Point location, Windows.Foundation.Size size) /// The y-coordinate of the upper-left corner of this structure. /// The x-coordinate of the lower-right corner of this structure. /// The y-coordinate of the lower-right corner of this structure. + /// The new that this method creates. public static Area FromLTRB(int left, int top, int right, int bottom) => new(left, top, unchecked(right - left), unchecked(bottom - top)); @@ -143,7 +144,7 @@ public Cords Location /// Gets or sets the coordinates of the center of the rectangular region represented by this /// . /// - public readonly Cords Center => unchecked(new(X + Width / 2, Y + Height / 2)); + public readonly Cords Center => unchecked(new(X + (Width / 2), Y + (Height / 2))); #if HAS_DRAWING /// @@ -533,12 +534,7 @@ public static Area Intersect(Area a, Area b) int y1 = Math.Max(a.Y, b.Y); int y2 = Math.Min(a.Y + a.Height, b.Y + b.Height); - if (x2 >= x1 && y2 >= y1) - { - return new Area(x1, y1, x2 - x1, y2 - y1); - } - - return Empty; + return x2 >= x1 && y2 >= y1 ? new Area(x1, y1, x2 - x1, y2 - y1) : Empty; } /// diff --git a/AdvancedSharpAdbClient/Models/ColorData.cs b/AdvancedSharpAdbClient/Models/ColorData.cs index d5eb5dae..cbb8cdda 100644 --- a/AdvancedSharpAdbClient/Models/ColorData.cs +++ b/AdvancedSharpAdbClient/Models/ColorData.cs @@ -29,7 +29,7 @@ public struct ColorData public uint Offset { get; set; } /// - /// Deconstruct the class. + /// Deconstruct the struct. /// /// The number of bits that contain information for this color. /// The offset, in bits, within the byte array for a pixel, at which the diff --git a/AdvancedSharpAdbClient/Models/Cords.cs b/AdvancedSharpAdbClient/Models/Cords.cs index 8df7d2d4..1e7e1219 100644 --- a/AdvancedSharpAdbClient/Models/Cords.cs +++ b/AdvancedSharpAdbClient/Models/Cords.cs @@ -16,9 +16,6 @@ public struct Cords : IEquatable /// public static readonly Cords Empty; - private int x; // Do not rename (binary serialization) - private int y; // Do not rename (binary serialization) - /// /// Initializes a new instance of the class. /// @@ -26,8 +23,8 @@ public struct Cords : IEquatable /// The vertical "Y" coordinate. public Cords(int cx, int cy) { - x = cx; - y = cy; + X = cx; + Y = cy; } /// @@ -35,8 +32,8 @@ public Cords(int cx, int cy) /// public Cords(int dw) { - x = LowInt16(dw); - y = HighInt16(dw); + X = LowInt16(dw); + Y = HighInt16(dw); } #if HAS_DRAWING @@ -45,8 +42,8 @@ public Cords(int dw) /// public Cords(System.Drawing.Point sz) { - x = sz.X; - y = sz.Y; + X = sz.X; + Y = sz.Y; } /// @@ -54,8 +51,8 @@ public Cords(System.Drawing.Point sz) /// public Cords(System.Drawing.Size sz) { - x = sz.Width; - y = sz.Height; + X = sz.Width; + Y = sz.Height; } #endif @@ -66,8 +63,8 @@ public Cords(System.Drawing.Size sz) /// public Cords(Windows.Foundation.Point sz) { - x = unchecked((int)sz.X); - y = unchecked((int)sz.Y); + X = unchecked((int)sz.X); + Y = unchecked((int)sz.Y); } /// @@ -75,8 +72,8 @@ public Cords(Windows.Foundation.Point sz) /// public Cords(Windows.Foundation.Size sz) { - x = unchecked((int)sz.Width); - y = unchecked((int)sz.Height); + X = unchecked((int)sz.Width); + Y = unchecked((int)sz.Height); } #pragma warning restore CS0419 // cref 特性中有不明确的引用 #endif @@ -84,25 +81,17 @@ public Cords(Windows.Foundation.Size sz) /// /// Gets a value indicating whether this is empty. /// - public readonly bool IsEmpty => x == 0 && y == 0; + public readonly bool IsEmpty => X == 0 && Y == 0; /// /// Gets or sets the horizontal "X" coordinate. /// - public int X - { - readonly get => x; - set => x = value; - } + public int X { readonly get; set; } /// /// Gets or sets the vertical "Y" coordinate. /// - public int Y - { - readonly get => y; - set => y = value; - } + public int Y { readonly get; set; } #if HAS_DRAWING /// @@ -333,9 +322,9 @@ public int Y /// An integer value that specifies a hash value for this . public override readonly int GetHashCode() => #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - HashCode.Combine(x, y); + HashCode.Combine(X, Y); #else - x ^ y; + X ^ Y; #endif /// @@ -371,8 +360,8 @@ public void Offset(int dx, int dy) /// The vertical "Y" coordinate. public readonly void Deconstruct(out int cx, out int cy) { - cx = x; - cy = y; + cx = X; + cy = Y; } private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); diff --git a/AdvancedSharpAdbClient/Models/DeviceDataEventArgs.cs b/AdvancedSharpAdbClient/Models/DeviceDataEventArgs.cs index 880400bf..d4d74ab9 100644 --- a/AdvancedSharpAdbClient/Models/DeviceDataEventArgs.cs +++ b/AdvancedSharpAdbClient/Models/DeviceDataEventArgs.cs @@ -3,6 +3,7 @@ // using System; +using System.Collections.Generic; namespace AdvancedSharpAdbClient { @@ -18,9 +19,73 @@ public class DeviceDataEventArgs : EventArgs public DeviceDataEventArgs(DeviceData device) => Device = device; /// - /// Gets the device. + /// Gets the device where the change occurred. /// - /// The device. - public DeviceData Device { get; private set; } + /// The device where the change occurred. + public DeviceData Device { get; } + } + + /// + /// The event arguments that are passed when a device event occurs. + /// + public class DeviceDataNotifyEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The list of device. + public DeviceDataNotifyEventArgs(IEnumerable devices) => Devices = devices; + + /// + /// Gets the list of device where the change occurred. + /// + /// The list of device where the change occurred. + public IEnumerable Devices { get; } + } + + /// + /// The event arguments that are passed when a device event occurs. + /// + public class DeviceDataConnectEventArgs : DeviceDataEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The device. + /// The device after the reported change. + public DeviceDataConnectEventArgs(DeviceData device, bool isConnect) : base(device) => IsConnect = isConnect; + + /// + /// Gets the connect state of the device after the reported change. + /// + public bool IsConnect { get; } + } + + /// + /// The event arguments that are passed when a device event occurs. + /// + public class DeviceDataChangeEventArgs : DeviceDataEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The device. + /// The state of the device after the reported change. + /// The state of the device before the reported change. + public DeviceDataChangeEventArgs(DeviceData device, DeviceState newState, DeviceState oldState) : base(device) + { + NewState = newState; + OldState = oldState; + } + + /// + /// Gets the state of the device after the reported change. + /// + public DeviceState NewState { get; } + + /// + /// Gets the state of the device before the reported change. + /// + public DeviceState OldState { get; } } } diff --git a/AdvancedSharpAdbClient/Models/Element.cs b/AdvancedSharpAdbClient/Models/Element.cs index 8b17d2f6..6a9b1ba1 100644 --- a/AdvancedSharpAdbClient/Models/Element.cs +++ b/AdvancedSharpAdbClient/Models/Element.cs @@ -3,7 +3,9 @@ // using System.Collections.Generic; +using System.Linq; using System.Threading; +using System.Xml; namespace AdvancedSharpAdbClient { @@ -13,29 +15,49 @@ namespace AdvancedSharpAdbClient public class Element { /// - /// The current ADB client that manages the connection. + /// Gets or sets the current ADB client that manages the connection. /// private IAdbClient Client { get; set; } /// - /// The current device containing the element. + /// Gets the current device containing the element. /// - private DeviceData Device { get; set; } + private DeviceData Device { get; } /// - /// The coordinates and size of the element. + /// Gets the coordinates and size of the element. /// - public Area Area { get; set; } + public Area Area { get; } /// - /// The coordinates of the element to click. Default is the center of area. + /// Gets or sets the coordinates of the element to click. Default is the center of area. /// public Cords Cords { get; set; } /// - /// Gets or sets element attributes. + /// Gets the children of this element. /// - public Dictionary Attributes { get; set; } + public List Children { get; } + + /// + /// Gets the element attributes. + /// + public Dictionary Attributes { get; } + + /// + /// Gets the of this element. + /// + public XmlNode Node { get; } + + /// + /// Gets or sets the element at the specified index. + /// + /// The zero-based index of the element to get or set. + /// The element at the specified index. + public Element this[int index] + { + get => Children[index]; + } /// /// Initializes a new instance of the class. @@ -68,6 +90,73 @@ public Element(IAdbClient client, DeviceData device, Area area, Dictionary + /// Initializes a new instance of the class. + /// + /// The current ADB client that manages the connection. + /// The current device containing the element. + /// The of the element. + /// The children of the element. + /// The coordinates and size of the element. + /// Gets or sets element attributes. + public Element(IAdbClient client, DeviceData device, XmlNode node, List children, Area area, Dictionary attributes) + { + Client = client; + Device = device; + Node = node; + Children = children; + Area = area; + Attributes = attributes; + Cords = area.Center; // Average x1, y1, x2, y2 + } + + /// + /// Creates a new with the specified . + /// + /// The current ADB client that manages the connection. + /// The current device containing the element. + /// The of the element. + /// The new that this method creates. + public static Element FromXmlNode(IAdbClient client, DeviceData device, XmlNode xmlNode) + { + string bounds = xmlNode.Attributes["bounds"].Value; + if (bounds != null) + { + int[] cords = bounds.Replace("][", ",").Replace("[", "").Replace("]", "").Split(',').Select(int.Parse).ToArray(); // x1, y1, x2, y2 + Dictionary attributes = new(xmlNode.Attributes.Count); + foreach (XmlAttribute at in xmlNode.Attributes) + { + attributes[at.Name] = at.Value; + } + Area area = Area.FromLTRB(cords[0], cords[1], cords[2], cords[3]); + XmlNodeList childNodes = xmlNode.ChildNodes; + List elements = new(childNodes?.Count ?? 0); + if (childNodes != null) + { + for (int i = 0; i < childNodes.Count; i++) + { + Element element = FromXmlNode(client, device, childNodes[i]); + if (element != null) + { + elements.Add(element); + } + } + } + return new Element(client, device, xmlNode, elements, area, attributes); + } + return null; + } + + /// + /// Gets the count of in this element. + /// + public int GetChildCount() + { + int count = Children.Count; + Children.ForEach(x => count += x.GetChildCount()); + return count; + } + /// /// Clicks on this coordinates. /// diff --git a/AdvancedSharpAdbClient/Models/ForwardData.cs b/AdvancedSharpAdbClient/Models/ForwardData.cs index 8661a1c1..96b1d4f4 100644 --- a/AdvancedSharpAdbClient/Models/ForwardData.cs +++ b/AdvancedSharpAdbClient/Models/ForwardData.cs @@ -34,6 +34,26 @@ public class ForwardData /// public ForwardSpec RemoteSpec => ForwardSpec.Parse(Remote); + /// + /// Initializes a new instance of the struct. + /// + public ForwardData() + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// The serial number of the device for which the port forwarding is configured. + /// The that represents the local (PC) endpoint. + /// The that represents the remote (device) endpoint. + public ForwardData(string serialNumber, string local, string remote) + { + SerialNumber = serialNumber; + Local = local; + Remote = remote; + } + /// /// Creates a new instance of the class by parsing a . /// @@ -45,14 +65,8 @@ public static ForwardData FromString(string value) { return null; } - string[] parts = value.Split(' '); - return new ForwardData() - { - SerialNumber = parts[0], - Local = parts[1], - Remote = parts[2] - }; + return new ForwardData(parts[0], parts[1], parts[2]); } /// diff --git a/AdvancedSharpAdbClient/Models/ForwardSpec.cs b/AdvancedSharpAdbClient/Models/ForwardSpec.cs index bdd9744b..305a69a6 100644 --- a/AdvancedSharpAdbClient/Models/ForwardSpec.cs +++ b/AdvancedSharpAdbClient/Models/ForwardSpec.cs @@ -142,26 +142,18 @@ public override int GetHashCode() => /// public override bool Equals(object obj) { - if (obj is not ForwardSpec other) - { - return false; - } - - if (other.Protocol != Protocol) - { - return false; - } - - return Protocol switch - { - ForwardProtocol.JavaDebugWireProtocol => ProcessId == other.ProcessId, - ForwardProtocol.Tcp => Port == other.Port, - ForwardProtocol.LocalAbstract - or ForwardProtocol.LocalFilesystem - or ForwardProtocol.LocalReserved - or ForwardProtocol.Device => string.Equals(SocketName, other.SocketName), - _ => false, - }; + return obj is ForwardSpec other + && other.Protocol == Protocol + && Protocol switch + { + ForwardProtocol.JavaDebugWireProtocol => ProcessId == other.ProcessId, + ForwardProtocol.Tcp => Port == other.Port, + ForwardProtocol.LocalAbstract + or ForwardProtocol.LocalFilesystem + or ForwardProtocol.LocalReserved + or ForwardProtocol.Device => string.Equals(SocketName, other.SocketName), + _ => false, + }; } } } diff --git a/AdvancedSharpAdbClient/Models/Framebuffer.cs b/AdvancedSharpAdbClient/Models/Framebuffer.cs index 33f7eac0..75061006 100644 --- a/AdvancedSharpAdbClient/Models/Framebuffer.cs +++ b/AdvancedSharpAdbClient/Models/Framebuffer.cs @@ -31,26 +31,26 @@ public Framebuffer(DeviceData device, AdbClient client) this.client = client ?? throw new ArgumentNullException(nameof(client)); // Initialize the headerData buffer - var size = Marshal.SizeOf(default(FramebufferHeader)); + int size = Marshal.SizeOf(default(FramebufferHeader)); this.headerData = new byte[size]; } /// /// Gets the device for which to fetch the frame buffer. /// - public DeviceData Device { get; private set; } + public DeviceData Device { get; set; } /// /// Gets the framebuffer header. The header contains information such as the width and height and the color encoding. /// This property is set after you call . /// - public FramebufferHeader Header { get; private set; } + public FramebufferHeader Header { get; set; } /// /// Gets the framebuffer data. You need to parse the to interpret this data (such as the color encoding). /// This property is set after you call . /// - public byte[] Data { get; private set; } + public byte[] Data { get; set; } /// /// Asynchronously refreshes the framebuffer: fetches the latest framebuffer data from the device. Access the @@ -116,7 +116,7 @@ public async Task RefreshAsync(CancellationToken cancellationToken = default) socket.ReadAdbResponse(); // The result first is a FramebufferHeader object, - await socket.ReadAsync(headerData, cancellationToken).ConfigureAwait(false); + _ = await socket.ReadAsync(headerData, cancellationToken).ConfigureAwait(false); if (!headerInitialized) { diff --git a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs index 6c914b1e..b805387a 100644 --- a/AdvancedSharpAdbClient/Models/FramebufferHeader.cs +++ b/AdvancedSharpAdbClient/Models/FramebufferHeader.cs @@ -14,10 +14,10 @@ namespace AdvancedSharpAdbClient /// of the framebuffer, prefixed with a object that contains more /// information about the framebuffer. /// - public struct FramebufferHeader + public class FramebufferHeader { /// - /// Gets or sets the version of the framebuffer structure. + /// Gets or sets the version of the framebuffer class. /// public uint Version { get; set; } @@ -69,12 +69,12 @@ public struct FramebufferHeader /// /// Creates a new object based on a byte array which contains the data. /// - /// The data that feeds the structure. + /// The data that feeds the class. /// A new object. public static FramebufferHeader Read(byte[] data) { // as defined in https://android.googlesource.com/platform/system/core/+/master/adb/framebuffer_service.cpp - FramebufferHeader header = default; + FramebufferHeader header = new(); // Read the data from a MemoryStream so we can use the BinaryReader to process the data. using (MemoryStream stream = new(data)) @@ -103,6 +103,7 @@ public static FramebufferHeader Read(byte[] data) header.Size = reader.ReadUInt32(); header.Width = reader.ReadUInt32(); header.Height = reader.ReadUInt32(); + header.Red = new ColorData { Offset = reader.ReadUInt32(), @@ -126,9 +127,9 @@ public static FramebufferHeader Read(byte[] data) Offset = reader.ReadUInt32(), Length = reader.ReadUInt32() }; - } - return header; + return header; + } } #if HAS_DRAWING @@ -143,7 +144,7 @@ public static FramebufferHeader Read(byte[] data) #if NET [SupportedOSPlatform("windows")] #endif - public readonly Image ToImage(byte[] buffer) + public Image ToImage(byte[] buffer) { ExceptionExtensions.ThrowIfNull(buffer); @@ -176,7 +177,7 @@ public readonly Image ToImage(byte[] buffer) #if NET [SupportedOSPlatform("windows")] #endif - private readonly PixelFormat StandardizePixelFormat(byte[] buffer) + private PixelFormat StandardizePixelFormat(byte[] buffer) { // Initial parameter validation. ExceptionExtensions.ThrowIfNull(buffer); @@ -287,7 +288,7 @@ private readonly PixelFormat StandardizePixelFormat(byte[] buffer) /// A that represents the image contained in the frame buffer, or /// if the framebuffer does not contain any data. This can happen when DRM is enabled on the device. /// - public readonly Task ToBitmap(byte[] buffer, CoreDispatcher dispatcher) + public Task ToBitmap(byte[] buffer, CoreDispatcher dispatcher) { FramebufferHeader self = this; @@ -325,7 +326,7 @@ public readonly Task ToBitmap(byte[] buffer, CoreDispatcher dis /// if the framebuffer does not contain any data. This can happen when DRM is enabled on the device. /// [ContractVersion(typeof(UniversalApiContract), 327680u)] - public readonly Task ToBitmap(byte[] buffer, DispatcherQueue dispatcher) + public Task ToBitmap(byte[] buffer, DispatcherQueue dispatcher) { FramebufferHeader self = this; @@ -364,7 +365,7 @@ public readonly Task ToBitmap(byte[] buffer, DispatcherQueue di /// A that represents the image contained in the frame buffer, or /// if the framebuffer does not contain any data. This can happen when DRM is enabled on the device. /// - public readonly async Task ToBitmap(byte[] buffer) + public async Task ToBitmap(byte[] buffer) { if (buffer == null) { diff --git a/AdvancedSharpAdbClient/Models/SyncProgressChangedEventArgs.cs b/AdvancedSharpAdbClient/Models/SyncProgressChangedEventArgs.cs index 23e8efbc..38cc3e7c 100644 --- a/AdvancedSharpAdbClient/Models/SyncProgressChangedEventArgs.cs +++ b/AdvancedSharpAdbClient/Models/SyncProgressChangedEventArgs.cs @@ -20,13 +20,13 @@ public class SyncProgressChangedEventArgs : EventArgs /// Gets the number of bytes sync to the local computer. /// /// An representing the number of sync bytes. - public long ReceivedBytesSize { get; internal set; } + public long ReceivedBytesSize { get; } /// /// Gets the total number of bytes for the sync operation. /// /// An representing the total size of the download, in bytes. - public long TotalBytesToReceive { get; internal set; } + public long TotalBytesToReceive { get; } internal SyncProgressChangedEventArgs(long received, long total) { diff --git a/AdvancedSharpAdbClient/Properties/AdvancedSharpAdbClient.rd.xml b/AdvancedSharpAdbClient/Properties/AdvancedSharpAdbClient.rd.xml index ea74d484..3e174abc 100644 --- a/AdvancedSharpAdbClient/Properties/AdvancedSharpAdbClient.rd.xml +++ b/AdvancedSharpAdbClient/Properties/AdvancedSharpAdbClient.rd.xml @@ -1,5 +1,5 @@ diff --git a/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs b/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs index 12879170..b98a1454 100644 --- a/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs +++ b/AdvancedSharpAdbClient/Receivers/IShellOutputReceiver.cs @@ -2,8 +2,6 @@ // Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. // -using System.Threading; - namespace AdvancedSharpAdbClient { /// diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index 16fb1808..98b55d0d 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -19,10 +19,10 @@ public partial class SyncService public async Task OpenAsync(CancellationToken cancellationToken = default) { // target a specific device - await Socket.SetDeviceAsync(Device, default); + await Socket.SetDeviceAsync(Device, cancellationToken); - await Socket.SendAdbRequestAsync("sync:", default); - _ = await Socket.ReadAdbResponseAsync(default); + await Socket.SendAdbRequestAsync("sync:", cancellationToken); + _ = await Socket.ReadAdbResponseAsync(cancellationToken); } /// @@ -179,7 +179,7 @@ public async Task PullAsync(string remoteFilePath, Stream stream, IProgress // The first 4 bytes contain the length of the data packet byte[] reply = new byte[4]; - await Socket.ReadAsync(reply, cancellationToken); + _ = await Socket.ReadAsync(reply, cancellationToken); if (!BitConverter.IsLittleEndian) { @@ -300,7 +300,7 @@ public async IAsyncEnumerable GetDirectoryAsyncListing(string re private async Task ReadStatisticsAsync(FileStatistics value, CancellationToken cancellationToken = default) { byte[] statResult = new byte[12]; - await Socket.ReadAsync(statResult, cancellationToken); + _ = await Socket.ReadAsync(statResult, cancellationToken); if (!BitConverter.IsLittleEndian) { diff --git a/AdvancedSharpAdbClient/TcpSocket.Async.cs b/AdvancedSharpAdbClient/TcpSocket.Async.cs new file mode 100644 index 00000000..c529f989 --- /dev/null +++ b/AdvancedSharpAdbClient/TcpSocket.Async.cs @@ -0,0 +1,35 @@ +#if HAS_TASK +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.Net.Sockets; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public partial class TcpSocket + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + public async Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => + await socket.SendAsync(buffer.AsMemory().Slice(offset, size), socketFlags, cancellationToken); +#else + /// + public async Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => + await Utilities.Run(() => Send(buffer, offset, size, socketFlags), cancellationToken); +#endif + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + public async Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => + await socket.ReceiveAsync(buffer.AsMemory().Slice(offset, size), socketFlags, cancellationToken); +#else + /// + public Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => + socket.ReceiveAsync(buffer, offset, size, socketFlags, cancellationToken); +#endif + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/TcpSocket.cs b/AdvancedSharpAdbClient/TcpSocket.cs index 4cdf5cbd..51d41f28 100644 --- a/AdvancedSharpAdbClient/TcpSocket.cs +++ b/AdvancedSharpAdbClient/TcpSocket.cs @@ -6,14 +6,13 @@ using System.IO; using System.Net; using System.Net.Sockets; -using System.Threading; namespace AdvancedSharpAdbClient { /// /// Implements the interface using the standard class. /// - public class TcpSocket : ITcpSocket + public partial class TcpSocket : ITcpSocket { private Socket socket; private EndPoint endPoint; @@ -78,28 +77,6 @@ public int Send(byte[] buffer, int offset, int size, SocketFlags socketFlags) => public int Receive(byte[] buffer, int offset, SocketFlags socketFlags) => socket.Receive(buffer, offset, socketFlags); -#if HAS_TASK -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - /// - public async Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => - await socket.SendAsync(buffer.AsMemory().Slice(offset, size), socketFlags, cancellationToken); -#else - /// - public async Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => - await Utilities.Run(() => Send(buffer, offset, size, socketFlags), cancellationToken); -#endif - -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - /// - public async Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => - await socket.ReceiveAsync(buffer.AsMemory().Slice(offset, size), socketFlags, cancellationToken); -#else - /// - public Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken = default) => - socket.ReceiveAsync(buffer, offset, size, socketFlags, cancellationToken); -#endif -#endif - /// public Stream GetStream() => new NetworkStream(socket); }