diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs index c95bcf51..fb08a73b 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.Async.cs @@ -640,7 +640,7 @@ public async void UnrootAsyncTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void InstallAsyncTest() @@ -688,7 +688,7 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void InstallMultipleAsyncTest() @@ -747,7 +747,7 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void InstallMultipleWithBaseAsyncTest() @@ -847,7 +847,7 @@ public async void InstallCreateAsyncTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void InstallWriteAsyncTest() @@ -920,6 +920,246 @@ await RunTestAsync( () => TestClient.InstallCommitAsync(Device, "936013062")); } +#if WINDOWS10_0_17763_0_OR_GREATER + /// + /// Tests the method. + /// + [Fact] + public async void InstallWinRTAsyncTest() + { + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; + + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } + + byte[] response = "Success\n"u8.ToArray(); + + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\TestApp\base.apk")); + using (IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync()) + { + string[] requests = + [ + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install' -S {stream.Size}" + ]; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + requests, + NoSyncRequests, + NoSyncResponses, + [response], + applicationDataChunks, + () => TestClient.InstallAsync(Device, stream, + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); + } + } + + /// + /// Tests the method. + /// + [Fact] + public async void InstallMultipleWinRTAsyncTest() + { + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; + + await using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } + + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\TestApp\split_config.arm64_v8a.apk")); + using IRandomAccessStreamWithContentType abiStream = await storageFile.OpenReadAsync(); + + string[] requests = + [ + "host:transport:169.254.109.177:5555", + "exec:cmd package 'install-create' -p com.google.android.gms", + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {abiStream.Size} 936013062 splitAPK0.apk", + "host:transport:169.254.109.177:5555", + "exec:cmd package 'install-commit' 936013062" + ]; + + byte[][] responses = + [ + Encoding.ASCII.GetBytes($"Success: streamed {abiStream.Size} bytes\n") + ]; + + await using MemoryStream sessionStream = new(Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n")); + await using MemoryStream commitStream = new("Success\n"u8.ToArray()); + + await RunTestAsync( + OkResponses(6), + NoResponseMessages, + requests, + NoSyncRequests, + NoSyncResponses, + responses, + applicationDataChunks, + [sessionStream, commitStream], + () => TestClient.InstallMultipleAsync(Device, [abiStream], "com.google.android.gms", + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.CreateSession, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); + } + + /// + /// Tests the method. + /// + [Fact] + public async void InstallMultipleWinRTWithBaseAsyncTest() + { + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; + + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } + + await using (FileStream stream = File.OpenRead("Assets/TestApp/split_config.arm64_v8a.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } + + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\TestApp\base.apk")); + using IRandomAccessStreamWithContentType baseStream = await storageFile.OpenReadAsync(); + storageFile = await StorageFile.GetFileFromPathAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\TestApp\split_config.arm64_v8a.apk")); + using IRandomAccessStreamWithContentType abiStream = await storageFile.OpenReadAsync(); + + string[] requests = + [ + "host:transport:169.254.109.177:5555", + "exec:cmd package 'install-create'", + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {baseStream.Size} 936013062 baseAPK.apk", + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {abiStream.Size} 936013062 splitAPK0.apk", + "host:transport:169.254.109.177:5555", + "exec:cmd package 'install-commit' 936013062" + ]; + + byte[][] responses = + [ + Encoding.ASCII.GetBytes($"Success: streamed {baseStream.Size} bytes\n"), + Encoding.ASCII.GetBytes($"Success: streamed {abiStream.Size} bytes\n") + ]; + + using MemoryStream sessionStream = new(Encoding.ASCII.GetBytes("Success: created install session [936013062]\r\n")); + using MemoryStream commitStream = new("Success\n"u8.ToArray()); + + await RunTestAsync( + OkResponses(8), + NoResponseMessages, + requests, + NoSyncRequests, + NoSyncResponses, + responses, + applicationDataChunks, + [sessionStream, commitStream], + () => TestClient.InstallMultipleAsync(Device, baseStream, [abiStream], + new InstallProgress( + PackageInstallProgressState.Preparing, + PackageInstallProgressState.CreateSession, + PackageInstallProgressState.Uploading, + PackageInstallProgressState.Installing, + PackageInstallProgressState.Finished))); + } + + /// + /// Tests the method. + /// + [Fact] + public async void InstallWriteWinRTAsyncTest() + { + // The app data is sent in chunks of 32 kb + List applicationDataChunks = []; + + await using (FileStream stream = File.OpenRead("Assets/TestApp/base.apk")) + { + byte[] buffer = new byte[32 * 1024]; + int read = 0; + + while ((read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length))) > 0) + { + byte[] array = buffer.AsSpan(0, read).ToArray(); + applicationDataChunks.Add(array); + } + } + + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\TestApp\base.apk")); + using (IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync()) + { + string[] requests = + [ + "host:transport:169.254.109.177:5555", + $"exec:cmd package 'install-write' -S {stream.Size} 936013062 base.apk" + ]; + + byte[] response = Encoding.ASCII.GetBytes($"Success: streamed {stream.Size} bytes\n"); + + double temp = 0; + Progress progress = new(); + progress.ProgressChanged += (sender, args) => + { + Assert.True(temp <= args, $"{nameof(args)}: {args} is less than {temp}."); + temp = args; + }; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + requests, + NoSyncRequests, + NoSyncResponses, + [response], + applicationDataChunks, + () => TestClient.InstallWriteAsync(Device, stream, "base", "936013062", progress)); + } + } +#endif + /// /// Tests the method. /// diff --git a/AdvancedSharpAdbClient.Tests/AdbClientTests.cs b/AdvancedSharpAdbClient.Tests/AdbClientTests.cs index 754f3841..b9def008 100644 --- a/AdvancedSharpAdbClient.Tests/AdbClientTests.cs +++ b/AdvancedSharpAdbClient.Tests/AdbClientTests.cs @@ -748,7 +748,7 @@ public void UnrootTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void InstallTest() @@ -796,7 +796,7 @@ public void InstallTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void InstallMultipleTest() @@ -871,7 +871,7 @@ public void InstallMultipleTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void InstallMultipleWithBaseTest() @@ -987,7 +987,7 @@ public void InstallCreateTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void InstallWriteTest() diff --git a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj index 112bf97e..e2510bfb 100644 --- a/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj +++ b/AdvancedSharpAdbClient.Tests/AdvancedSharpAdbClient.Tests.csproj @@ -29,8 +29,8 @@ - - + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs index 094da8d6..bcd4d383 100644 --- a/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs +++ b/AdvancedSharpAdbClient.Tests/DeviceCommands/Models/VersionInfoTests.cs @@ -34,7 +34,7 @@ public void TryAsVersionTest(int versionCode, string versionName, bool expected) #if WINDOWS10_0_17763_0_OR_GREATER /// - /// Tests the method. + /// Tests the method. /// [Theory] [InlineData(1231, "1.2.3.1", true)] @@ -45,7 +45,7 @@ public void TryAsVersionTest(int versionCode, string versionName, bool expected) [InlineData(098765456, "Unknown", false)] public void TryAsPackageVersionTest(int versionCode, string versionName, bool expected) { - bool result = new VersionInfo(versionCode, versionName).TryAsPackageVersion(out Windows.ApplicationModel.PackageVersion version); + bool result = new VersionInfo(versionCode, versionName).TryAsPackageVersion(out PackageVersion version); Assert.Equal(expected, result); if (expected) { diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs index 148bc6c3..073c2d08 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummyAdbClient.cs @@ -171,9 +171,9 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.GetFrameBufferAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); - void IAdbClient.Install(DeviceData device, Stream apk, IProgress progress, params string[] arguments) => throw new NotImplementedException(); + void IAdbClient.Install(DeviceData device, Stream apk, Action progress, params string[] arguments) => throw new NotImplementedException(); - Task IAdbClient.InstallAsync(DeviceData device, Stream apk, IProgress progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); + Task IAdbClient.InstallAsync(DeviceData device, Stream apk, Action progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); void IAdbClient.InstallCommit(DeviceData device, string session) => throw new NotImplementedException(); @@ -183,17 +183,17 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.InstallCreateAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); - void IAdbClient.InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress progress, params string[] arguments) => throw new NotImplementedException(); + void IAdbClient.InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, Action progress, params string[] arguments) => throw new NotImplementedException(); - void IAdbClient.InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress progress, params string[] arguments) => throw new NotImplementedException(); + void IAdbClient.InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, Action progress, params string[] arguments) => throw new NotImplementedException(); - Task IAdbClient.InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); + Task IAdbClient.InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, Action progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); - Task IAdbClient.InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); + Task IAdbClient.InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, Action progress, CancellationToken cancellationToken, params string[] arguments) => throw new NotImplementedException(); - void IAdbClient.InstallWrite(DeviceData device, Stream apk, string apkName, string session, IProgress progress) => throw new NotImplementedException(); + void IAdbClient.InstallWrite(DeviceData device, Stream apk, string apkName, string session, Action progress) => throw new NotImplementedException(); - Task IAdbClient.InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, IProgress progress, CancellationToken cancellationToken) => throw new NotImplementedException(); + Task IAdbClient.InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, Action progress, CancellationToken cancellationToken) => throw new NotImplementedException(); void IAdbClient.KillAdb() => throw new NotImplementedException(); @@ -235,7 +235,7 @@ public Task ExecuteServerCommandAsync(string target, string command, IAdbSocket Task IAdbClient.RootAsync(DeviceData device, CancellationToken cancellationToken) => throw new NotImplementedException(); - void IAdbClient.RunLogService(DeviceData device, Action messageSink, params LogId[] logNames) => throw new NotImplementedException(); + void IAdbClient.RunLogService(DeviceData device, Action messageSink, in bool isCancelled, params LogId[] logNames) => throw new NotImplementedException(); Task IAdbClient.RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) => throw new NotImplementedException(); diff --git a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs index e54ebc2f..ff2d77b2 100644 --- a/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs +++ b/AdvancedSharpAdbClient.Tests/Dummys/DummySyncService.cs @@ -25,24 +25,24 @@ public async Task OpenAsync(CancellationToken cancellationToken = default) IsOpen = true; } - public void Pull(string remotePath, Stream stream, IProgress progress = null, in bool isCancelled = false) + public void Pull(string remotePath, Stream stream, Action progress = null, in bool isCancelled = false) { for (int i = 0; i <= 100; i++) { - progress?.Report(new SyncProgressChangedEventArgs(i, 100)); + progress?.Invoke(new SyncProgressChangedEventArgs(i, 100)); } } - public async Task PullAsync(string remotePath, Stream stream, IProgress progress, CancellationToken cancellationToken = default) + public async Task PullAsync(string remotePath, Stream stream, Action progress, CancellationToken cancellationToken = default) { - await Task.Yield(); for (int i = 0; i <= 100; i++) { - progress?.Report(new SyncProgressChangedEventArgs(i, 100)); + await Task.Yield(); + progress?.Invoke(new SyncProgressChangedEventArgs(i, 100)); } } - public void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress progress = null, in bool isCancelled = false) + public void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action progress = null, in bool isCancelled = false) { for (int i = 0; i <= 100; i++) { @@ -50,20 +50,20 @@ public void Push(Stream stream, string remotePath, int permissions, DateTimeOffs { UploadedFiles[remotePath] = stream; } - progress?.Report(new SyncProgressChangedEventArgs(i, 100)); + progress?.Invoke(new SyncProgressChangedEventArgs(i, 100)); } } - public async Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress progress, CancellationToken cancellationToken = default) + public async Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action progress, CancellationToken cancellationToken = default) { - await Task.Yield(); for (int i = 0; i <= 100; i++) { + await Task.Yield(); if (i == 100) { UploadedFiles[remotePath] = stream; } - progress?.Report(new SyncProgressChangedEventArgs(i, 100)); + progress?.Invoke(new SyncProgressChangedEventArgs(i, 100)); } } diff --git a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs index 72ab9c3c..5198d70d 100644 --- a/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs +++ b/AdvancedSharpAdbClient.Tests/Logs/LogTests.cs @@ -21,8 +21,8 @@ public void ReadLogTest() Assert.IsType(log); Assert.Equal(707, log.ProcessId); - Assert.Equal(1254, log.ThreadId); - Assert.Equal(3u, log.Id); + Assert.Equal(1254u, log.ThreadId); + Assert.Equal((LogId)3, 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); @@ -50,8 +50,8 @@ public async void ReadLogAsyncTest() Assert.IsType(log); Assert.Equal(707, log.ProcessId); - Assert.Equal(1254, log.ThreadId); - Assert.Equal(3u, log.Id); + Assert.Equal(1254u, log.ThreadId); + Assert.Equal((LogId)3, 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); @@ -76,8 +76,8 @@ public void ReadEventLogTest() Assert.IsType(entry); Assert.Equal(707, entry.ProcessId); - Assert.Equal(1291, entry.ThreadId); - Assert.Equal(2u, entry.Id); + Assert.Equal(1291u, entry.ThreadId); + Assert.Equal((LogId)2, 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); @@ -110,8 +110,8 @@ public async void ReadEventLogAsyncTest() Assert.IsType(entry); Assert.Equal(707, entry.ProcessId); - Assert.Equal(1291, entry.ThreadId); - Assert.Equal(2u, entry.Id); + Assert.Equal(1291u, entry.ThreadId); + Assert.Equal((LogId)2, 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); diff --git a/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs b/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs index bccc2545..07658998 100644 --- a/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs +++ b/AdvancedSharpAdbClient.Tests/Properties/GlobalUsings.cs @@ -11,4 +11,11 @@ #region AdvancedSharpAdbClient.Tests global using AdvancedSharpAdbClient.Tests; global using AdvancedSharpAdbClient.Logs.Tests; -#endregion \ No newline at end of file +#endregion + +#if WINDOWS10_0_17763_0_OR_GREATER +global using Windows.ApplicationModel; +global using System.Runtime.InteropServices.WindowsRuntime; +global using Windows.Storage; +global using Windows.Storage.Streams; +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs index c7b1e540..e6a38d1d 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.Async.cs @@ -142,7 +142,7 @@ public async void GetAsyncListingTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void PullAsyncTest() @@ -177,7 +177,7 @@ await RunTestAsync( } /// - /// Tests the method. + /// Tests the method. /// [Fact] public async void PushAsyncTest() @@ -208,5 +208,77 @@ await RunTestAsync( await service.PushAsync(stream, "/sdcard/test", 0644, new DateTime(2015, 11, 2, 23, 0, 0, DateTimeKind.Utc), null); }); } + +#if WINDOWS10_0_17763_0_OR_GREATER + /// + /// Tests the method. + /// + [Fact] + public async void PullWinRTAsyncTest() + { + using InMemoryRandomAccessStream stream = new(); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); + byte[] contentLength = BitConverter.GetBytes(content.Length); + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.STAT, "/fstab.donatello"), + (SyncCommand.RECV, "/fstab.donatello") + ], + [SyncCommand.STAT, SyncCommand.DATA, SyncCommand.DONE], + [ + [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); + }); + + IBuffer buffer = await stream.GetInputStreamAt(0).ReadAsync(new byte[(int)stream.Size].AsBuffer(), (uint)stream.Size, InputStreamOptions.None); + // Make sure the data that has been sent to the stream is the expected data + Assert.Equal(content, buffer.ToArray()); + } + + /// + /// Tests the method. + /// + [Fact] + public async void PushWinRTAsyncTest() + { + StorageFile storageFile = await StorageFile.GetFileFromPathAsync(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\Fstab.bin")); + using IRandomAccessStreamWithContentType stream = await storageFile.OpenReadAsync(); + byte[] content = await File.ReadAllBytesAsync("Assets/Fstab.bin"); + byte[] contentMessage = + [ + .. SyncCommand.DATA.GetBytes(), + .. BitConverter.GetBytes(content.Length), + .. content, + ]; + + await RunTestAsync( + OkResponses(2), + NoResponseMessages, + ["host:transport:169.254.109.177:5555", "sync:"], + [ + (SyncCommand.SEND, "/sdcard/test,644"), + (SyncCommand.DONE, "1446505200") + ], + [SyncCommand.OKAY], + null, + [contentMessage], + 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); + }); + } +#endif } } diff --git a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs index 6791a809..88a4a159 100644 --- a/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs +++ b/AdvancedSharpAdbClient.Tests/SyncServiceTests.cs @@ -98,7 +98,7 @@ public void GetListingTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PullTest() @@ -133,7 +133,7 @@ public void PullTest() } /// - /// Tests the method. + /// Tests the method. /// [Fact] public void PushTest() diff --git a/AdvancedSharpAdbClient/AdbClient.Async.cs b/AdvancedSharpAdbClient/AdbClient.Async.cs index 3caa4128..75f4e483 100644 --- a/AdvancedSharpAdbClient/AdbClient.Async.cs +++ b/AdvancedSharpAdbClient/AdbClient.Async.cs @@ -274,7 +274,7 @@ public virtual async Task GetFrameBufferAsync(DeviceData device, Ca } /// - public virtual async Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken, params LogId[] logNames) + public virtual async Task RunLogServiceAsync(DeviceData device, Action messageSink, CancellationToken cancellationToken = default, params LogId[] logNames) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(messageSink); @@ -401,9 +401,9 @@ protected async Task RootAsync(string request, DeviceData device, CancellationTo string responseMessage = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif // see https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/daemon/restart_service.cpp @@ -421,9 +421,9 @@ protected async Task RootAsync(string request, DeviceData device, CancellationTo } /// - public virtual async Task InstallAsync(DeviceData device, Stream apk, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallAsync(DeviceData device, Stream apk, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); @@ -469,30 +469,30 @@ public virtual async Task InstallAsync(DeviceData device, Stream apk, IProgress< await socket.SendAsync(buffer, read, cancellationToken).ConfigureAwait(false); #endif totalBytesRead += read; - progress?.Report(new InstallProgressEventArgs(0, 1, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess)); + progress?.Invoke(new InstallProgressEventArgs(0, 1, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess)); } - progress?.Report(new InstallProgressEventArgs(1, 1, 100)); + progress?.Invoke(new InstallProgressEventArgs(1, 1, 100)); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); string value = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif if (!value.Contains("Success")) { throw new AdbException(value); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public virtual async Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(baseAPK); @@ -508,12 +508,12 @@ public virtual async Task InstallMultipleAsync(DeviceData device, Stream baseAPK throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable and seekable stream"); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); string session = await InstallCreateAsync(device, null, cancellationToken, arguments).ConfigureAwait(false); int splitAPKsCount = splitAPKs.Count(); void OnMainSyncProgressChanged(string? sender, double args) => - progress?.Report(new InstallProgressEventArgs(sender == null ? 1 : 0, splitAPKsCount + 1, args / 2)); + progress?.Invoke(new InstallProgressEventArgs(sender == null ? 1 : 0, splitAPKsCount + 1, args / 2)); await InstallWriteAsync(device, baseAPK, nameof(baseAPK), session, OnMainSyncProgressChanged, cancellationToken).ConfigureAwait(false); @@ -531,22 +531,22 @@ void OnSplitSyncProgressChanged(string? sender, double args) { status[path] = args; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); } } int i = 0; await splitAPKs.Select(splitAPK => InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSplitSyncProgressChanged, cancellationToken)).WhenAll().ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); await InstallCommitAsync(device, session, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public virtual async Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(splitAPKs); @@ -557,7 +557,7 @@ public virtual async Task InstallMultipleAsync(DeviceData device, IEnumerable x / splitAPKsCount).Sum())); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitAPKsCount, status.Values.Select(x => x / splitAPKsCount).Sum())); } } int i = 0; await splitAPKs.Select(splitAPK => InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSyncProgressChanged, cancellationToken)).WhenAll().ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); await InstallCommitAsync(device, session, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public virtual async Task InstallCreateAsync(DeviceData device, string? packageName, CancellationToken cancellationToken, params string[] arguments) + public virtual async Task InstallCreateAsync(DeviceData device, string? packageName, CancellationToken cancellationToken = default, params string[] arguments) { EnsureDevice(device); @@ -628,9 +628,9 @@ public virtual async Task InstallCreateAsync(DeviceData device, string? } /// - public virtual async Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, IProgress? progress = null, CancellationToken cancellationToken = default) + public virtual async Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, Action? progress = null, CancellationToken cancellationToken = default) { - progress?.Report(0); + progress?.Invoke(0); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); @@ -671,22 +671,22 @@ public virtual async Task InstallWriteAsync(DeviceData device, Stream apk, strin await socket.SendAsync(buffer, read, cancellationToken).ConfigureAwait(false); #endif totalBytesRead += read; - progress?.Report(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); + progress?.Invoke(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); } read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); string value = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif if (!value.Contains("Success")) { throw new AdbException(value); } - progress?.Report(100); + progress?.Invoke(100); } /// @@ -750,9 +750,9 @@ protected virtual async Task InstallWriteAsync(DeviceData device, Stream apk, st read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); string value = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif if (!value.Contains("Success")) @@ -779,8 +779,297 @@ public virtual async Task InstallCommitAsync(DeviceData device, string session, } } +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER /// - public virtual async Task UninstallAsync(DeviceData device, string packageName, CancellationToken cancellationToken, params string[] arguments) + public virtual async Task InstallAsync(DeviceData device, IRandomAccessStream apk, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(apk); + + if (!apk.CanRead) + { + throw new ArgumentOutOfRangeException(nameof(apk), "The apk stream must be a readable stream"); + } + + StringBuilder requestBuilder = new StringBuilder().Append("exec:cmd package 'install'"); + + if (arguments != null) + { + foreach (string argument in arguments) + { + _ = requestBuilder.AppendFormat(" {0}", argument); + } + } + + // add size parameter [required for streaming installs] + // do last to override any user specified value + _ = requestBuilder.Append($" -S {apk.Size}"); + + using IAdbSocket socket = AdbSocketFactory(EndPoint); + await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); + + await socket.SendAdbRequestAsync(requestBuilder.ToString(), cancellationToken).ConfigureAwait(false); + _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); + + byte[] buffer = new byte[32 * 1024]; + + ulong totalBytesToProcess = apk.Size; + ulong totalBytesRead = 0; + + while (true) + { + IBuffer results = await apk.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None).AsTask(cancellationToken).ConfigureAwait(false); + if (results.Length == 0) { break; } + await socket.SendAsync(buffer.AsMemory(0, (int)results.Length), cancellationToken).ConfigureAwait(false); + totalBytesRead += results.Length; + progress?.Invoke(new InstallProgressEventArgs(0, 1, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess)); + } + progress?.Invoke(new InstallProgressEventArgs(1, 1, 100)); + + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + int read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + string value = +#if HAS_BUFFERS + Encoding.GetString(buffer.AsSpan(0, read)); +#else + Encoding.GetString(buffer, 0, read); +#endif + + if (!value.Contains("Success")) + { + throw new AdbException(value); + } + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + } + + /// + public virtual async Task InstallMultipleAsync(DeviceData device, IRandomAccessStream baseAPK, IEnumerable splitAPKs, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(baseAPK); + ExceptionExtensions.ThrowIfNull(splitAPKs); + + if (!baseAPK.CanRead) + { + throw new ArgumentOutOfRangeException(nameof(baseAPK), "The apk stream must be a readable stream"); + } + + if (splitAPKs.Any(apk => apk == null || !apk.CanRead)) + { + throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable stream"); + } + + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + string session = await InstallCreateAsync(device, null, cancellationToken, arguments).ConfigureAwait(false); + + int splitAPKsCount = splitAPKs.Count(); + void OnMainSyncProgressChanged(string? sender, double args) => + progress?.Invoke(new InstallProgressEventArgs(sender == null ? 1 : 0, splitAPKsCount + 1, args / 2)); + + await InstallWriteAsync(device, baseAPK, nameof(baseAPK), session, OnMainSyncProgressChanged, cancellationToken).ConfigureAwait(false); + + int progressCount = 1; + Dictionary status = new(splitAPKsCount); + void OnSplitSyncProgressChanged(string? sender, double args) + { + lock (status) + { + if (sender == null) + { + progressCount++; + } + else if (sender is string path) + { + status[path] = args; + } + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); + } + } + + int i = 0; + await splitAPKs.Select(splitAPK => InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSplitSyncProgressChanged, cancellationToken)).WhenAll().ConfigureAwait(false); + + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + await InstallCommitAsync(device, session, cancellationToken).ConfigureAwait(false); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + } + + /// + public virtual async Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(splitAPKs); + ExceptionExtensions.ThrowIfNull(packageName); + + if (splitAPKs.Any(apk => apk == null || !apk.CanRead)) + { + throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable stream"); + } + + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + string session = await InstallCreateAsync(device, packageName, cancellationToken, arguments).ConfigureAwait(false); + + int progressCount = 0; + int splitAPKsCount = splitAPKs.Count(); + Dictionary status = new(splitAPKsCount); + void OnSyncProgressChanged(string? sender, double args) + { + lock (status) + { + if (sender == null) + { + progressCount++; + } + else if (sender is string path) + { + status[path] = args; + } + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitAPKsCount, status.Values.Select(x => x / splitAPKsCount).Sum())); + } + } + + int i = 0; + await splitAPKs.Select(splitAPK => InstallWriteAsync(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSyncProgressChanged, cancellationToken)).WhenAll().ConfigureAwait(false); + + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + await InstallCommitAsync(device, session, cancellationToken).ConfigureAwait(false); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + } + + /// + public virtual async Task InstallWriteAsync(DeviceData device, IRandomAccessStream apk, string apkName, string session, Action? progress = null, CancellationToken cancellationToken = default) + { + progress?.Invoke(0); + + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(apk); + ExceptionExtensions.ThrowIfNull(apkName); + ExceptionExtensions.ThrowIfNull(session); + + if (!apk.CanRead) + { + throw new ArgumentOutOfRangeException(nameof(apk), "The apk stream must be a readable stream"); + } + + StringBuilder requestBuilder = + new StringBuilder().Append($"exec:cmd package 'install-write'") + // add size parameter [required for streaming installs] + // do last to override any user specified value + .AppendFormat(" -S {0}", apk.Size) + .AppendFormat(" {0} {1}.apk", session, apkName); + + using IAdbSocket socket = AdbSocketFactory(EndPoint); + await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); + + await socket.SendAdbRequestAsync(requestBuilder.ToString(), cancellationToken).ConfigureAwait(false); + _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); + + byte[] buffer = new byte[32 * 1024]; + + ulong totalBytesToProcess = apk.Size; + ulong totalBytesRead = 0; + + while (true) + { + IBuffer results = await apk.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None).AsTask(cancellationToken).ConfigureAwait(false); + if (results.Length == 0) { break; } + await socket.SendAsync(buffer.AsMemory(0, (int)results.Length), cancellationToken).ConfigureAwait(false); + totalBytesRead += results.Length; + progress?.Invoke(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); + } + + int read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + string value = +#if HAS_BUFFERS + Encoding.GetString(buffer.AsSpan(0, read)); +#else + Encoding.GetString(buffer, 0, read); +#endif + + if (!value.Contains("Success")) + { + throw new AdbException(value); + } + progress?.Invoke(100); + } + + /// + /// Asynchronously write an apk into the given install session. + /// + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + protected virtual async Task InstallWriteAsync(DeviceData device, IRandomAccessStream apk, string apkName, string session, Action? progress, CancellationToken cancellationToken = default) + { + progress?.Invoke(apkName, 0); + + EnsureDevice(device); + ExceptionExtensions.ThrowIfNull(apk); + ExceptionExtensions.ThrowIfNull(apkName); + ExceptionExtensions.ThrowIfNull(session); + + if (!apk.CanRead) + { + throw new ArgumentOutOfRangeException(nameof(apk), "The apk stream must be a readable stream"); + } + + StringBuilder requestBuilder = + new StringBuilder().Append($"exec:cmd package 'install-write'") + // add size parameter [required for streaming installs] + // do last to override any user specified value + .AppendFormat(" -S {0}", apk.Size) + .AppendFormat(" {0} {1}.apk", session, apkName); + + using IAdbSocket socket = AdbSocketFactory(EndPoint); + await socket.SetDeviceAsync(device, cancellationToken).ConfigureAwait(false); + + await socket.SendAdbRequestAsync(requestBuilder.ToString(), cancellationToken).ConfigureAwait(false); + _ = await socket.ReadAdbResponseAsync(cancellationToken).ConfigureAwait(false); + + byte[] buffer = new byte[32 * 1024]; + + ulong totalBytesToProcess = apk.Size; + ulong totalBytesRead = 0; + + while (true) + { + IBuffer results = await apk.ReadAsync(buffer.AsBuffer(), (uint)buffer.Length, InputStreamOptions.None).AsTask(cancellationToken).ConfigureAwait(false); + if (results.Length == 0) { break; } + await socket.SendAsync(buffer.AsMemory(0, (int)results.Length), cancellationToken).ConfigureAwait(false); + totalBytesRead += results.Length; + progress?.Invoke(apkName, totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); + } + progress?.Invoke(apkName, 100); + + int read = await socket.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + string value = +#if HAS_BUFFERS + Encoding.GetString(buffer.AsSpan(0, read)); +#else + Encoding.GetString(buffer, 0, read); +#endif + + if (!value.Contains("Success")) + { + throw new AdbException(value); + } + progress?.Invoke(null, 100); + } +#endif + + /// + public virtual async Task UninstallAsync(DeviceData device, string packageName, CancellationToken cancellationToken = default, params string[] arguments) { EnsureDevice(device); diff --git a/AdvancedSharpAdbClient/AdbClient.cs b/AdvancedSharpAdbClient/AdbClient.cs index 6cf12fcd..fbe4e033 100644 --- a/AdvancedSharpAdbClient/AdbClient.cs +++ b/AdvancedSharpAdbClient/AdbClient.cs @@ -29,6 +29,9 @@ namespace AdvancedSharpAdbClient /// adb.c /// public partial class AdbClient : IAdbClient +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + , IAdbClient.IWinRT +#endif { /// /// The of s that represent a new line. @@ -99,7 +102,7 @@ public AdbClient(EndPoint endPoint, Func adbSocketFactory) } EndPoint = endPoint; - this.AdbSocketFactory = adbSocketFactory ?? throw new ArgumentNullException(nameof(adbSocketFactory)); + AdbSocketFactory = adbSocketFactory ?? throw new ArgumentNullException(nameof(adbSocketFactory)); } /// @@ -426,7 +429,7 @@ public Framebuffer GetFrameBuffer(DeviceData device) } /// - public virtual void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames) + public virtual void RunLogService(DeviceData device, Action messageSink, in bool isCancelled = false, params LogId[] logNames) { EnsureDevice(device); ExceptionExtensions.ThrowIfNull(messageSink); @@ -449,7 +452,7 @@ public virtual void RunLogService(DeviceData device, Action messageSin using Stream stream = socket.GetShellStream(); LogReader reader = new(stream); - while (true) + while (!isCancelled) { LogEntry? entry = null; @@ -547,9 +550,9 @@ protected void Root(string request, DeviceData device) string responseMessage = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif // see https://android.googlesource.com/platform/packages/modules/adb/+/refs/heads/master/daemon/restart_service.cpp @@ -571,9 +574,9 @@ protected void Root(string request, DeviceData device) } /// - public void Install(DeviceData device, Stream apk, IProgress? progress = null, params string[] arguments) + public void Install(DeviceData device, Stream apk, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); @@ -619,30 +622,30 @@ public void Install(DeviceData device, Stream apk, IProgress - public void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, params string[] arguments) + public void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(baseAPK); @@ -658,12 +661,12 @@ public void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable - progress?.Report(new InstallProgressEventArgs(sender == null ? 1 : 0, splitAPKsCount + 1, args / 2)); + progress?.Invoke(new InstallProgressEventArgs(sender == null ? 1 : 0, splitAPKsCount + 1, args / 2)); InstallWrite(device, baseAPK, nameof(baseAPK), session, OnMainSyncProgressChanged); @@ -681,7 +684,7 @@ void OnSplitSyncProgressChanged(string? sender, double args) { status[path] = args; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitAPKsCount + 1, (status.Values.Select(x => x / splitAPKsCount).Sum() + 100) / 2)); } } @@ -691,15 +694,15 @@ void OnSplitSyncProgressChanged(string? sender, double args) InstallWrite(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSplitSyncProgressChanged); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallCommit(device, session); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// - public void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, params string[] arguments) + public void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(splitAPKs); @@ -710,7 +713,7 @@ public void InstallMultiple(DeviceData device, IEnumerable splitAPKs, st throw new ArgumentOutOfRangeException(nameof(splitAPKs), "The apk stream must be a readable and seekable stream"); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); string session = InstallCreate(device, packageName, arguments); int progressCount = 0; @@ -728,7 +731,7 @@ void OnSyncProgressChanged(string? sender, double args) { status[path] = args; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitAPKsCount, status.Values.Select(x => x / splitAPKsCount).Sum())); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitAPKsCount, status.Values.Select(x => x / splitAPKsCount).Sum())); } } @@ -738,9 +741,9 @@ void OnSyncProgressChanged(string? sender, double args) InstallWrite(device, splitAPK, $"{nameof(splitAPK)}{i++}", session, OnSyncProgressChanged); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallCommit(device, session); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -783,9 +786,9 @@ public string InstallCreate(DeviceData device, string? packageName = null, param } /// - public void InstallWrite(DeviceData device, Stream apk, string apkName, string session, IProgress? progress = null) + public void InstallWrite(DeviceData device, Stream apk, string apkName, string session, Action? progress = null) { - progress?.Report(0); + progress?.Invoke(0); EnsureDevice(device); ExceptionExtensions.ThrowIfNull(apk); @@ -826,22 +829,22 @@ public void InstallWrite(DeviceData device, Stream apk, string apkName, string s socket.Send(buffer, read); #endif totalBytesRead += read; - progress?.Report(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); + progress?.Invoke(totalBytesToProcess == 0 ? 0 : totalBytesRead * 100d / totalBytesToProcess); } read = socket.Read(buffer); string value = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif if (!value.Contains("Success")) { throw new AdbException(value); } - progress?.Report(100); + progress?.Invoke(100); } /// @@ -902,9 +905,9 @@ protected virtual void InstallWrite(DeviceData device, Stream apk, string apkNam read = socket.Read(buffer); string value = #if HAS_BUFFERS - Encoding.UTF8.GetString(buffer.AsSpan(0, read)); + Encoding.GetString(buffer.AsSpan(0, read)); #else - Encoding.UTF8.GetString(buffer, 0, read); + Encoding.GetString(buffer, 0, read); #endif if (!value.Contains("Success")) diff --git a/AdvancedSharpAdbClient/AdbServer.cs b/AdvancedSharpAdbClient/AdbServer.cs index 6d16e6d9..76e7db1f 100644 --- a/AdvancedSharpAdbClient/AdbServer.cs +++ b/AdvancedSharpAdbClient/AdbServer.cs @@ -117,8 +117,8 @@ public AdbServer(EndPoint endPoint, Func adbSocketFactory, } EndPoint = endPoint; - this.AdbSocketFactory = adbSocketFactory ?? throw new ArgumentNullException(nameof(adbSocketFactory)); - this.AdbCommandLineClientFactory = adbCommandLineClientFactory ?? throw new ArgumentNullException(nameof(adbCommandLineClientFactory)); + AdbSocketFactory = adbSocketFactory ?? throw new ArgumentNullException(nameof(adbSocketFactory)); + AdbCommandLineClientFactory = adbCommandLineClientFactory ?? throw new ArgumentNullException(nameof(adbCommandLineClientFactory)); } /// diff --git a/AdvancedSharpAdbClient/AdbSocket.Async.cs b/AdvancedSharpAdbClient/AdbSocket.Async.cs index d141747f..3102b21a 100644 --- a/AdvancedSharpAdbClient/AdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/AdbSocket.Async.cs @@ -221,17 +221,7 @@ public virtual async Task ReadSyncStringAsync(CancellationToken cancella byte[] reply = new byte[4]; _ = await ReadAsync(reply, cancellationToken).ConfigureAwait(false); - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(reply); - } - - int len = -#if HAS_BUFFERS - BitConverter.ToInt32(reply); -#else - BitConverter.ToInt32(reply, 0); -#endif + int len = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); // And get the string reply = new byte[len]; diff --git a/AdvancedSharpAdbClient/AdbSocket.cs b/AdvancedSharpAdbClient/AdbSocket.cs index 5d53fbed..05abd2a2 100644 --- a/AdvancedSharpAdbClient/AdbSocket.cs +++ b/AdvancedSharpAdbClient/AdbSocket.cs @@ -294,16 +294,7 @@ public virtual string ReadSyncString() byte[] reply = new byte[4]; _ = Read(reply); - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(reply); - } - -#if HAS_BUFFERS - int len = BitConverter.ToInt32(reply); -#else - int len = BitConverter.ToInt32(reply, 0); -#endif + int len = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); // And get the string reply = new byte[len]; @@ -503,7 +494,7 @@ protected virtual string ReplyToString(byte[] reply) string result; try { - result = Encoding.ASCII.GetString(reply); + result = AdbClient.Encoding.GetString(reply); } catch (DecoderFallbackException e) { @@ -524,7 +515,7 @@ protected virtual string ReplyToString(ReadOnlySpan reply) string result; try { - result = Encoding.ASCII.GetString(reply); + result = AdbClient.Encoding.GetString(reply); } catch (DecoderFallbackException e) { diff --git a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj index 5958db9d..22ce9bdb 100644 --- a/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj +++ b/AdvancedSharpAdbClient/AdvancedSharpAdbClient.csproj @@ -58,6 +58,7 @@ + @@ -65,7 +66,7 @@ - + which represents the asynchronous operation. public static async Task PullAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, - IProgress? progress = null, CancellationToken cancellationToken = default) + Action? progress = null, + CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); await service.PullAsync(remotePath, stream, progress, cancellationToken).ConfigureAwait(false); @@ -246,7 +247,8 @@ public static async Task PullAsync(this IAdbClient client, DeviceData device, /// A which represents the asynchronous operation. public static async Task PushAsync(this IAdbClient client, DeviceData device, string remotePath, Stream stream, int permissions, DateTimeOffset timestamp, - IProgress? progress = null, CancellationToken cancellationToken = default) + Action? progress = null, + CancellationToken cancellationToken = default) { using ISyncService service = Factories.SyncServiceFactory(client, device); await service.PushAsync(stream, remotePath, permissions, timestamp, progress, cancellationToken).ConfigureAwait(false); @@ -295,6 +297,100 @@ public static async Task> GetEnvironmentVariablesAsyn return receiver.EnvironmentVariables; } + /// + /// Asynchronously installs an Android application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute file system path to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install. + /// A which represents the asynchronous operation. + public static Task InstallPackageAsync(this IAdbClient client, DeviceData device, string packageFilePath, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.InstallPackageAsync(packageFilePath, progress, cancellationToken, arguments); + } + + /// + /// Asynchronously installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute base app file system path to file on local host to install. + /// The absolute split app file system paths to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceData device, string basePackageFilePath, IEnumerable splitPackageFilePaths, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.InstallMultiplePackageAsync(basePackageFilePath, splitPackageFilePaths, progress, cancellationToken, arguments); + } + + /// + /// Asynchronously installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute split app file system paths to file on local host to install. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceData device, IEnumerable splitPackageFilePaths, string packageName, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + return manager.InstallMultiplePackageAsync(splitPackageFilePaths, packageName, progress, cancellationToken, arguments); + } + +#if !NETFRAMEWORK || NET40_OR_GREATER + /// + /// Asynchronously 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 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, + IProgress? progress = null, + CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + await service.PullAsync(remotePath, stream, progress == null ? null : progress.Report, cancellationToken).ConfigureAwait(false); + } + + /// + /// Asynchronously 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 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, + IProgress? progress = null, + CancellationToken cancellationToken = default) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + await service.PushAsync(stream, remotePath, permissions, timestamp, progress == null ? null : progress.Report, cancellationToken).ConfigureAwait(false); + } + /// /// Asynchronously installs an Android application on device. /// @@ -347,6 +443,7 @@ public static Task InstallMultiplePackageAsync(this IAdbClient client, DeviceDat PackageManager manager = new(client, device, skipInit: true); return manager.InstallMultiplePackageAsync(splitPackageFilePaths, packageName, progress, cancellationToken, arguments); } +#endif /// /// Asynchronously uninstalls a package from the device. diff --git a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs index b1231189..fae6b6fa 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/DeviceExtensions.cs @@ -204,7 +204,7 @@ public static IEnumerable List(this IAdbClient client, DeviceDat /// A that can be used to cancel the task. public static void Pull(this IAdbClient client, DeviceData device, string remotePath, Stream stream, - IProgress? progress = null, + Action? progress = null, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); @@ -224,7 +224,7 @@ public static void Pull(this IAdbClient client, DeviceData device, /// A that can be used to cancel the task. public static void Push(this IAdbClient client, DeviceData device, string remotePath, Stream stream, int permissions, DateTimeOffset timestamp, - IProgress? progress = null, + Action? progress = null, in bool isCancelled = false) { using ISyncService service = Factories.SyncServiceFactory(client, device); @@ -271,6 +271,92 @@ public static Dictionary GetEnvironmentVariables(this IAdbClient return receiver.EnvironmentVariables; } + /// + /// Installs an Android application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute file system path to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install. + public static void InstallPackage(this IAdbClient client, DeviceData device, string packageFilePath, Action? progress = null, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + manager.InstallPackage(packageFilePath, progress, arguments); + } + + /// + /// Installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute base app file system path to file on local host to install. + /// The absolute split app file system paths to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public static void InstallMultiplePackage(this IAdbClient client, DeviceData device, string basePackageFilePath, IEnumerable splitPackageFilePaths, Action? progress = null, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + manager.InstallMultiplePackage(basePackageFilePath, splitPackageFilePaths, progress, arguments); + } + + /// + /// Installs Android multiple application on device. + /// + /// The connection to the adb server. + /// The device on which to uninstall the package. + /// The absolute split app file system paths to file on local host to install. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public static void InstallMultiplePackage(this IAdbClient client, DeviceData device, IEnumerable splitPackageFilePaths, string packageName, Action? progress = null, params string[] arguments) + { + PackageManager manager = new(client, device, skipInit: true); + manager.InstallMultiplePackage(splitPackageFilePaths, packageName, progress, arguments); + } + +#if !NETFRAMEWORK || NET40_OR_GREATER + /// + /// 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 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. + public static void Pull(this IAdbClient client, DeviceData device, + string remotePath, Stream stream, + IProgress? progress = null, + in bool isCancelled = false) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + service.Pull(remotePath, stream, progress == null ? null : progress.Report, in isCancelled); + } + + /// + /// 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 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. + public static void Push(this IAdbClient client, DeviceData device, + string remotePath, Stream stream, int permissions, DateTimeOffset timestamp, + IProgress? progress = null, + in bool isCancelled = false) + { + using ISyncService service = Factories.SyncServiceFactory(client, device); + service.Push(stream, remotePath, permissions, timestamp, progress == null ? null : progress.Report, in isCancelled); + } + /// /// Installs an Android application on device. /// @@ -317,6 +403,7 @@ public static void InstallMultiplePackage(this IAdbClient client, DeviceData dev PackageManager manager = new(client, device, skipInit: true); manager.InstallMultiplePackage(splitPackageFilePaths, packageName, progress, arguments); } +#endif /// /// Uninstalls a package from the device. diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs index 3bd85ed8..2282f375 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.Async.cs @@ -47,24 +47,24 @@ public virtual Task RefreshPackagesAsync(CancellationToken cancellationToken = d /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install. /// A which represents the asynchronous operation. - public virtual async Task InstallPackageAsync(string packageFilePath, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallPackageAsync(string packageFilePath, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); ValidateDevice(); void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => - progress?.Report(new InstallProgressEventArgs(sender is null ? 1 : 0, 1, args.ProgressPercentage)); + progress?.Invoke(new InstallProgressEventArgs(sender is null ? 1 : 0, 1, args.ProgressPercentage)); string remoteFilePath = await SyncPackageToDeviceAsync(packageFilePath, OnSyncProgressChanged, cancellationToken).ConfigureAwait(false); await InstallRemotePackageAsync(remoteFilePath, progress, cancellationToken, arguments).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(0, 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(0, 1, PackageInstallProgressState.PostInstall)); await RemoveRemotePackageAsync(remoteFilePath, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(1, 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(1, 1, PackageInstallProgressState.PostInstall)); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -76,9 +76,9 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install. /// A which represents the asynchronous operation. - public virtual async Task InstallRemotePackageAsync(string remoteFilePath, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallRemotePackageAsync(string remoteFilePath, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); ValidateDevice(); @@ -114,16 +114,16 @@ public virtual async Task InstallRemotePackageAsync(string remoteFilePath, IProg /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. - public virtual async Task InstallMultiplePackageAsync(string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallMultiplePackageAsync(string basePackageFilePath, IEnumerable splitPackageFilePaths, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); ValidateDevice(); int splitPackageFileCount = splitPackageFilePaths.Count(); void OnMainSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => - progress?.Report(new InstallProgressEventArgs(sender is null ? 1 : 0, splitPackageFileCount + 1, args.ProgressPercentage / 2)); + progress?.Invoke(new InstallProgressEventArgs(sender is null ? 1 : 0, splitPackageFileCount + 1, args.ProgressPercentage / 2)); string baseRemoteFilePath = await SyncPackageToDeviceAsync(basePackageFilePath, OnMainSyncProgressChanged, cancellationToken).ConfigureAwait(false); @@ -147,7 +147,7 @@ void OnSplitSyncProgressChanged(string? sender, SyncProgressChangedEventArgs arg } status[path] = args.ProgressPercentage; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitPackageFileCount + 1, (status.Values.Select(x => x / splitPackageFileCount).Sum() + 100) / 2)); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitPackageFileCount + 1, (status.Values.Select(x => x / splitPackageFileCount).Sum() + 100) / 2)); } } @@ -160,13 +160,13 @@ void OnSplitSyncProgressChanged(string? sender, SyncProgressChangedEventArgs arg await InstallMultipleRemotePackageAsync(baseRemoteFilePath, splitRemoteFilePaths, progress, cancellationToken, arguments); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); int count = 0; await splitRemoteFilePaths.Select(async x => { count++; await RemoveRemotePackageAsync(x, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); }).WhenAll().ConfigureAwait(false); if (count < splitRemoteFilePaths.Length) @@ -175,9 +175,9 @@ await splitRemoteFilePaths.Select(async x => } await RemoveRemotePackageAsync(baseRemoteFilePath, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -190,9 +190,9 @@ await splitRemoteFilePaths.Select(async x => /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. - public virtual async Task InstallMultiplePackageAsync(IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallMultiplePackageAsync(IEnumerable splitPackageFilePaths, string packageName, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); ValidateDevice(); @@ -218,7 +218,7 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) } status[path] = args.ProgressPercentage; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitPackageFileCount, status.Values.Select(x => x / splitPackageFileCount).Sum())); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitPackageFileCount, status.Values.Select(x => x / splitPackageFileCount).Sum())); } } @@ -231,13 +231,13 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) await InstallMultipleRemotePackageAsync(splitRemoteFilePaths, packageName, progress, cancellationToken, arguments); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); int count = 0; await splitRemoteFilePaths.Select(async x => { count++; await RemoveRemotePackageAsync(x, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(count, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(count, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); }).WhenAll().ConfigureAwait(false); if (count < splitRemoteFilePaths.Length) @@ -245,7 +245,7 @@ await splitRemoteFilePaths.Select(async x => throw new PackageInstallationException($"{nameof(RemoveRemotePackageAsync)} failed. {splitRemoteFilePaths.Length} should process but only {count} processed."); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -258,9 +258,9 @@ await splitRemoteFilePaths.Select(async x => /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. - public virtual async Task InstallMultipleRemotePackageAsync(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallMultipleRemotePackageAsync(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); ValidateDevice(); @@ -268,17 +268,17 @@ public virtual async Task InstallMultipleRemotePackageAsync(string baseRemoteFil int splitRemoteFileCount = splitRemoteFilePaths.Count(); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); await WriteInstallSessionAsync(session, "base", baseRemoteFilePath, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(1, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(1, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); int count = 0; await splitRemoteFilePaths.Select(async (splitRemoteFilePath) => { await WriteInstallSessionAsync(session, $"split{count++}", splitRemoteFilePath, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(count, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(count, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); }).WhenAll().ConfigureAwait(false); if (count < splitRemoteFileCount) @@ -286,7 +286,7 @@ await splitRemoteFilePaths.Select(async (splitRemoteFilePath) => throw new PackageInstallationException($"{nameof(WriteInstallSessionAsync)} failed. {splitRemoteFileCount} should process but only {count} processed."); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallOutputReceiver receiver = new(); await AdbClient.ExecuteShellCommandAsync(Device, $"pm install-commit {session}", receiver, cancellationToken).ConfigureAwait(false); @@ -307,9 +307,9 @@ await splitRemoteFilePaths.Select(async (splitRemoteFilePath) => /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to pm install-create. /// A which represents the asynchronous operation. - public virtual async Task InstallMultipleRemotePackageAsync(IEnumerable splitRemoteFilePaths, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) + public virtual async Task InstallMultipleRemotePackageAsync(IEnumerable splitRemoteFilePaths, string packageName, Action? progress = null, CancellationToken cancellationToken = default, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); ValidateDevice(); @@ -317,13 +317,13 @@ public virtual async Task InstallMultipleRemotePackageAsync(IEnumerable int splitRemoteFileCount = splitRemoteFilePaths.Count(); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); int count = 0; await splitRemoteFilePaths.Select(async (splitRemoteFilePath) => { await WriteInstallSessionAsync(session, $"split{count++}", splitRemoteFilePath, cancellationToken).ConfigureAwait(false); - progress?.Report(new InstallProgressEventArgs(count, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(count, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); }).WhenAll().ConfigureAwait(false); if (count < splitRemoteFileCount) @@ -331,7 +331,7 @@ await splitRemoteFilePaths.Select(async (splitRemoteFilePath) => throw new PackageInstallationException($"{nameof(WriteInstallSessionAsync)} failed. {splitRemoteFileCount} should process but only {count} processed."); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallOutputReceiver receiver = new(); await AdbClient.ExecuteShellCommandAsync(Device, $"pm install-commit {session}", receiver, cancellationToken).ConfigureAwait(false); @@ -342,6 +342,84 @@ await splitRemoteFilePaths.Select(async (splitRemoteFilePath) => } } +#if !NETFRAMEWORK || NET40_OR_GREATER + /// + /// Asynchronously installs an Android application on device. + /// + /// The absolute file system path to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install. + /// A which represents the asynchronous operation. + public virtual async Task InstallPackageAsync(string packageFilePath, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + await InstallPackageAsync(packageFilePath, progress == null ? null : progress.Report, cancellationToken, arguments).ConfigureAwait(false); + + /// + /// Asynchronously installs the application package that was pushed to a temporary location on the device. + /// + /// absolute file path to package file on device. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install. + /// A which represents the asynchronous operation. + public virtual async Task InstallRemotePackageAsync(string remoteFilePath, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + await InstallRemotePackageAsync(remoteFilePath, progress == null ? null : progress.Report, cancellationToken, arguments).ConfigureAwait(false); + + /// + /// Asynchronously installs Android multiple application on device. + /// + /// The absolute base app file system path to file on local host to install. + /// The absolute split app file system paths to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public virtual async Task InstallMultiplePackageAsync(string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + await InstallMultiplePackageAsync(basePackageFilePath, splitPackageFilePaths, progress == null ? null : progress.Report, cancellationToken, arguments).ConfigureAwait(false); + + /// + /// Asynchronously installs Android multiple application on device. + /// + /// The absolute split app file system paths to file on local host to install. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public virtual async Task InstallMultiplePackageAsync(IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + await InstallMultiplePackageAsync(splitPackageFilePaths, packageName, progress == null ? null : progress.Report, cancellationToken, arguments).ConfigureAwait(false); + + /// + /// Asynchronously installs the multiple application package that was pushed to a temporary location on the device. + /// + /// The absolute base app file path to package file on device. + /// The absolute split app file paths to package file on device. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public virtual async Task InstallMultipleRemotePackageAsync(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + await InstallMultipleRemotePackageAsync(baseRemoteFilePath, splitRemoteFilePaths, progress == null ? null : progress.Report, cancellationToken, arguments).ConfigureAwait(false); + + /// + /// Asynchronously installs the multiple application package that was pushed to a temporary location on the device. + /// + /// The absolute split app file paths to package file on device. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to pm install-create. + /// A which represents the asynchronous operation. + public virtual async Task InstallMultipleRemotePackageAsync(IEnumerable splitRemoteFilePaths, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + await InstallMultipleRemotePackageAsync(splitRemoteFilePaths, packageName, progress == null ? null : progress.Report, cancellationToken, arguments).ConfigureAwait(false); +#endif + /// /// Asynchronously uninstalls a package from the device. /// @@ -445,7 +523,7 @@ protected virtual async Task SyncPackageToDeviceAsync(string localFilePa logger.LogDebug("Uploading file onto device '{0}'", Device.Serial); - SyncProgress? syncProgress = progress == null ? null : new SyncProgress(localFilePath, progress); + Action? syncProgress = progress == null ? null : args => progress.Invoke(localFilePath, args); // 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).ConfigureAwait(false); diff --git a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs index 611f44d0..5f078bc0 100644 --- a/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs +++ b/AdvancedSharpAdbClient/DeviceCommands/PackageManager.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; using System.Text; @@ -57,7 +56,7 @@ public PackageManager(IAdbClient client, DeviceData device, FuncAn optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install. - public virtual void InstallPackage(string packageFilePath, IProgress? progress = null, params string[] arguments) + public virtual void InstallPackage(string packageFilePath, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); ValidateDevice(); void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => - progress?.Report(new InstallProgressEventArgs(sender is null ? 1 : 0, 1, args.ProgressPercentage)); + progress?.Invoke(new InstallProgressEventArgs(sender is null ? 1 : 0, 1, args.ProgressPercentage)); string remoteFilePath = SyncPackageToDevice(packageFilePath, OnSyncProgressChanged); InstallRemotePackage(remoteFilePath, progress, arguments); - progress?.Report(new InstallProgressEventArgs(0, 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(0, 1, PackageInstallProgressState.PostInstall)); RemoveRemotePackage(remoteFilePath); - progress?.Report(new InstallProgressEventArgs(1, 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(1, 1, PackageInstallProgressState.PostInstall)); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -169,9 +168,9 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install. - public virtual void InstallRemotePackage(string remoteFilePath, IProgress? progress = null, params string[] arguments) + public virtual void InstallRemotePackage(string remoteFilePath, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); ValidateDevice(); @@ -205,16 +204,16 @@ public virtual void InstallRemotePackage(string remoteFilePath, IProgressAn optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. - public virtual void InstallMultiplePackage(string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, params string[] arguments) + public virtual void InstallMultiplePackage(string basePackageFilePath, IEnumerable splitPackageFilePaths, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); ValidateDevice(); int splitPackageFileCount = splitPackageFilePaths.Count(); void OnMainSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) => - progress?.Report(new InstallProgressEventArgs(sender is null ? 1 : 0, splitPackageFileCount + 1, args.ProgressPercentage / 2)); + progress?.Invoke(new InstallProgressEventArgs(sender is null ? 1 : 0, splitPackageFileCount + 1, args.ProgressPercentage / 2)); string baseRemoteFilePath = SyncPackageToDevice(basePackageFilePath, OnMainSyncProgressChanged); @@ -232,7 +231,7 @@ void OnSplitSyncProgressChanged(string? sender, SyncProgressChangedEventArgs arg { status[path] = args.ProgressPercentage; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitPackageFileCount + 1, (status.Values.Select(x => x / splitPackageFileCount).Sum() + 100) / 2)); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitPackageFileCount + 1, (status.Values.Select(x => x / splitPackageFileCount).Sum() + 100) / 2)); } } @@ -246,17 +245,17 @@ void OnSplitSyncProgressChanged(string? sender, SyncProgressChangedEventArgs arg InstallMultipleRemotePackage(baseRemoteFilePath, splitRemoteFilePaths, progress, arguments); int count = 0; - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); foreach (string splitRemoteFilePath in splitRemoteFilePaths) { RemoveRemotePackage(splitRemoteFilePath); - progress?.Report(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); } RemoveRemotePackage(baseRemoteFilePath); - progress?.Report(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length + 1, PackageInstallProgressState.PostInstall)); - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -267,9 +266,9 @@ void OnSplitSyncProgressChanged(string? sender, SyncProgressChangedEventArgs arg /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. - public virtual void InstallMultiplePackage(IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, params string[] arguments) + public virtual void InstallMultiplePackage(IEnumerable splitPackageFilePaths, string packageName, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Preparing)); ValidateDevice(); @@ -289,7 +288,7 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) { status[path] = args.ProgressPercentage; } - progress?.Report(new InstallProgressEventArgs(progressCount, splitPackageFileCount, status.Values.Select(x => x / splitPackageFileCount).Sum())); + progress?.Invoke(new InstallProgressEventArgs(progressCount, splitPackageFileCount, status.Values.Select(x => x / splitPackageFileCount).Sum())); } } @@ -302,15 +301,15 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) InstallMultipleRemotePackage(splitRemoteFilePaths, packageName, progress, arguments); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); int count = 0; foreach (string splitRemoteFilePath in splitRemoteFilePaths) { RemoveRemotePackage(splitRemoteFilePath); - progress?.Report(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); + progress?.Invoke(new InstallProgressEventArgs(++count, splitRemoteFilePaths.Length, PackageInstallProgressState.PostInstall)); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Finished)); } /// @@ -321,9 +320,9 @@ void OnSyncProgressChanged(string? sender, SyncProgressChangedEventArgs args) /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. - public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, IProgress? progress = null, params string[] arguments) + public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); ValidateDevice(); @@ -331,20 +330,20 @@ public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnu int splitRemoteFileCount = splitRemoteFilePaths.Count(); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); WriteInstallSession(session, "base", baseRemoteFilePath); - progress?.Report(new InstallProgressEventArgs(1, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(1, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); int count = 0; foreach (string splitRemoteFilePath in splitRemoteFilePaths) { WriteInstallSession(session, $"split{count++}", splitRemoteFilePath); - progress?.Report(new InstallProgressEventArgs(count, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(count, splitRemoteFileCount + 1, PackageInstallProgressState.WriteSession)); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallOutputReceiver receiver = new(); AdbClient.ExecuteShellCommand(Device, $"pm install-commit {session}", receiver); @@ -363,9 +362,9 @@ public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnu /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to pm install-create. - public virtual void InstallMultipleRemotePackage(IEnumerable splitRemoteFilePaths, string packageName, IProgress? progress = null, params string[] arguments) + public virtual void InstallMultipleRemotePackage(IEnumerable splitRemoteFilePaths, string packageName, Action? progress = null, params string[] arguments) { - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.CreateSession)); ValidateDevice(); @@ -373,16 +372,16 @@ public virtual void InstallMultipleRemotePackage(IEnumerable splitRemote int splitRemoteFileCount = splitRemoteFilePaths.Count(); - progress?.Report(new InstallProgressEventArgs(0, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(0, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); int count = 0; foreach (string splitRemoteFilePath in splitRemoteFilePaths) { WriteInstallSession(session, $"split{count++}", splitRemoteFilePath); - progress?.Report(new InstallProgressEventArgs(count, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); + progress?.Invoke(new InstallProgressEventArgs(count, splitRemoteFileCount, PackageInstallProgressState.WriteSession)); } - progress?.Report(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); + progress?.Invoke(new InstallProgressEventArgs(PackageInstallProgressState.Installing)); InstallOutputReceiver receiver = new(); AdbClient.ExecuteShellCommand(Device, $"pm install-commit {session}", receiver); @@ -393,6 +392,72 @@ public virtual void InstallMultipleRemotePackage(IEnumerable splitRemote } } +#if !NETFRAMEWORK || NET40_OR_GREATER + /// + /// Installs an Android application on device. + /// + /// The absolute file system path to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install. + public virtual void InstallPackage(string packageFilePath, IProgress? progress = null, params string[] arguments) => + InstallPackage(packageFilePath, progress == null ? null : progress.Report, arguments); + + /// + /// Installs the application package that was pushed to a temporary location on the device. + /// + /// absolute file path to package file on device. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install. + public virtual void InstallRemotePackage(string remoteFilePath, IProgress? progress = null, params string[] arguments) => + InstallRemotePackage(remoteFilePath, progress == null ? null : progress.Report, arguments); + + /// + /// Installs Android multiple application on device. + /// + /// The absolute base app file system path to file on local host to install. + /// The absolute split app file system paths to file on local host to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public virtual void InstallMultiplePackage(string basePackageFilePath, IEnumerable splitPackageFilePaths, IProgress? progress = null, params string[] arguments) => + InstallMultiplePackage(basePackageFilePath, splitPackageFilePaths, progress == null ? null : progress.Report, arguments); + + /// + /// Installs Android multiple application on device. + /// + /// The absolute split app file system paths to file on local host to install. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public virtual void InstallMultiplePackage(IEnumerable splitPackageFilePaths, string packageName, IProgress? progress = null, params string[] arguments) => + InstallMultiplePackage(splitPackageFilePaths, packageName, progress == null ? null : progress.Report, arguments); + + /// + /// Installs the multiple application package that was pushed to a temporary location on the device. + /// + /// The absolute base app file path to package file on device. + /// The absolute split app file paths to package file on device. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public virtual void InstallMultipleRemotePackage(string baseRemoteFilePath, IEnumerable splitRemoteFilePaths, IProgress? progress = null, params string[] arguments) => + InstallMultipleRemotePackage(baseRemoteFilePath, splitRemoteFilePaths, progress == null ? null : progress.Report, arguments); + + /// + /// Installs the multiple application package that was pushed to a temporary location on the device. + /// + /// The absolute split app file paths to package file on device. + /// The absolute package name of the base app. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to pm install-create. + public virtual void InstallMultipleRemotePackage(IEnumerable splitRemoteFilePaths, string packageName, IProgress? progress = null, params string[] arguments) => + InstallMultipleRemotePackage(splitRemoteFilePaths, packageName, progress == null ? null : progress.Report, arguments); +#endif + /// /// Uninstalls a package from the device. /// @@ -490,7 +555,7 @@ protected virtual string SyncPackageToDevice(string localFilePath, Action? syncProgress = progress == null ? null : args => progress.Invoke(localFilePath, args); // As C# can't use octal, the octal literal 666 (rw-Permission) is here converted to decimal (438) sync.Push(stream, remoteFilePath, 438, File.GetLastWriteTime(localFilePath), syncProgress, false); @@ -587,17 +652,5 @@ protected virtual void WriteInstallSession(string session, string apkName, strin throw new PackageInstallationException(receiver.ErrorMessage); } } - - /// - /// The used for . - /// - /// The absolute path to file on local host. - /// An optional parameter which, when specified, returns progress notifications. - [EditorBrowsable(EditorBrowsableState.Never)] - protected readonly struct SyncProgress(string localFilePath, Action progress) : IProgress - { - /// - public void Report(SyncProgressChangedEventArgs value) => progress?.Invoke(localFilePath, value); - } } } diff --git a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs index f66782fb..310b2d03 100644 --- a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs +++ b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.Async.cs @@ -4,12 +4,14 @@ // using System; +using System.Collections.Generic; +using System.IO; using System.Net; using System.Threading; namespace AdvancedSharpAdbClient { - public static partial class AdbClientExtensions + public partial class AdbClientExtensions { /// /// Asynchronously asks the ADB server to forward local connections from @@ -266,6 +268,151 @@ public static Task ConnectAsync(this IAdbClient client, IPEndPoint endpo public static Task ConnectAsync(this IAdbClient client, string host, int port = AdbClient.DefaultPort, CancellationToken cancellationToken = default) => client.ConnectAsync(Extensions.CreateDnsEndPoint(host, port), cancellationToken); +#if !NETFRAMEWORK || NET40_OR_GREATER + /// + /// Asynchronously runs the event log service on a device. + /// + /// An instance of a class that implements the interface. + /// 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. + public static Task RunLogServiceAsync(this IAdbClient client, DeviceData device, IProgress messageSink, params LogId[] logNames) => + client.RunLogServiceAsync(device, messageSink.Report, default, logNames); + + /// + /// Asynchronously runs the event log service on a device. + /// + /// An instance of a class that implements the interface. + /// The device on which to run the event log service. + /// A callback which will receive the event log messages as they are received. + /// A which can be used to cancel the asynchronous operation. + /// Optionally, the names of the logs to receive. + /// A which represents the asynchronous operation. + public static Task RunLogServiceAsync(this IAdbClient client, DeviceData device, IProgress messageSink, CancellationToken cancellationToken, params LogId[] logNames) => + client.RunLogServiceAsync(device, messageSink.Report, cancellationToken, logNames); + + /// + /// Asynchronously installs an Android application on an device. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the application to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install. + /// A which represents the asynchronous operation. + public static Task InstallAsync(this IAdbClient client, DeviceData device, Stream apk, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + client.InstallAsync(device, apk, progress == null ? null : progress.Report, cancellationToken, arguments); + + /// + /// Asynchronously push multiple APKs to the device and install them. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the base APK to install. + /// s which represents the split APKs to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultipleAsync(this IAdbClient client, DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + client.InstallMultipleAsync(device, baseAPK, splitAPKs, progress == null ? null : progress.Report, cancellationToken, arguments); + + /// + /// Asynchronously push multiple APKs to the device and install them. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// s which represents the split APKs to install. + /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultipleAsync(this IAdbClient client, DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + client.InstallMultipleAsync(device, splitAPKs, packageName, progress == null ? null : progress.Report, cancellationToken, arguments); + + /// + /// Asynchronously write an apk into the given install session. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task InstallWriteAsync(this IAdbClient client, DeviceData device, Stream apk, string apkName, string session, IProgress? progress = null, CancellationToken cancellationToken = default) => + client.InstallWriteAsync(device, apk, apkName, session, progress == null ? null : progress.Report, cancellationToken); + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + /// Asynchronously installs an Android application on an device. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the application to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install. + /// A which represents the asynchronous operation. + public static Task InstallAsync(this IAdbClient.IWinRT client, DeviceData device, IRandomAccessStream apk, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + client.InstallAsync(device, apk, progress == null ? null : progress.Report, cancellationToken, arguments); + + /// + /// Asynchronously push multiple APKs to the device and install them. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the base APK to install. + /// s which represents the split APKs to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultipleAsync(this IAdbClient.IWinRT client, DeviceData device, IRandomAccessStream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + client.InstallMultipleAsync(device, baseAPK, splitAPKs, progress == null ? null : progress.Report, cancellationToken, arguments); + + /// + /// Asynchronously push multiple APKs to the device and install them. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// s which represents the split APKs to install. + /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install-create. + /// A which represents the asynchronous operation. + public static Task InstallMultipleAsync(this IAdbClient.IWinRT client, DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, CancellationToken cancellationToken = default, params string[] arguments) => + client.InstallMultipleAsync(device, splitAPKs, packageName, progress == null ? null : progress.Report, cancellationToken, arguments); + + /// + /// Asynchronously write an apk into the given install session. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + public static Task InstallWriteAsync(this IAdbClient.IWinRT client, DeviceData device, IRandomAccessStream apk, string apkName, string session, IProgress? progress = null, CancellationToken cancellationToken = default) => + client.InstallWriteAsync(device, apk, apkName, session, progress == null ? null : progress.Report, cancellationToken); +#endif +#endif + /// /// Like "install", but starts an install session synchronously. /// diff --git a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs index 20f79380..75a2c35c 100644 --- a/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs +++ b/AdvancedSharpAdbClient/Extensions/AdbClientExtensions.cs @@ -3,6 +3,8 @@ // using System; +using System.Collections.Generic; +using System.IO; using System.Net; namespace AdvancedSharpAdbClient @@ -27,6 +29,33 @@ public static partial class AdbClientExtensions public static int CreateForward(this IAdbClient client, DeviceData device, ForwardSpec local, ForwardSpec remote, bool allowRebind) => client.CreateForward(device, local.ToString(), remote.ToString(), allowRebind); + /// + /// 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 + /// 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); + + /// + /// 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. + /// 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) => + client.CreateForward(device, $"tcp:{localPort}", $"local:{remoteSocket}", true); + /// /// Asks the ADB server to reverse forward local connections from /// to the address on the . @@ -115,31 +144,14 @@ public static void ExecuteRemoteCommand(this IAdbClient client, string command, client.ExecuteRemoteCommand(command, device, receiver, AdbClient.Encoding); /// - /// 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 - /// 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); - - /// - /// Forwards a remote Unix socket to a local TCP socket. + /// Runs the event log service on a device. /// /// 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. - /// 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) => - client.CreateForward(device, $"tcp:{localPort}", $"local:{remoteSocket}", true); + /// 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. + public static void RunLogService(this IAdbClient client, DeviceData device, Action messageSink, params LogId[] logNames) => + client.RunLogService(device, messageSink, false, logNames); /// /// Reboots the specified adb socket address. @@ -224,5 +236,79 @@ public static string Connect(this IAdbClient client, IPEndPoint endpoint) => /// The results from adb. public static string Connect(this IAdbClient client, string host, int port = AdbClient.DefaultPort) => client.Connect(Extensions.CreateDnsEndPoint(host, port)); + +#if !NETFRAMEWORK || NET40_OR_GREATER + /// + /// Runs the event log service on a device. + /// + /// An instance of a class that implements the interface. + /// 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. + public static void RunLogService(this IAdbClient client, DeviceData device, IProgress messageSink, params LogId[] logNames) => + client.RunLogService(device, messageSink, false, logNames); + + /// + /// Runs the event log service on a device. + /// + /// An instance of a class that implements the interface. + /// The device on which to run the event log service. + /// A callback which will receive the event log messages as they are received. + /// A that can be used to cancel the task. + /// Optionally, the names of the logs to receive. + public static void RunLogService(this IAdbClient client, DeviceData device, IProgress messageSink, in bool isCancelled, params LogId[] logNames) => + client.RunLogService(device, messageSink.Report, isCancelled, logNames); + + /// + /// Installs an Android application on an device. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the application to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install. + public static void Install(this IAdbClient client, DeviceData device, Stream apk, IProgress? progress = null, params string[] arguments) => + client.Install(device, apk, progress == null ? null : progress.Report, arguments); + + /// + /// Push multiple APKs to the device and install them. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the base APK to install. + /// s which represents the split APKs to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install-create. + public static void InstallMultiple(this IAdbClient client, DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress = null, params string[] arguments) => + client.InstallMultiple(device, baseAPK, splitAPKs, progress == null ? null : progress.Report, arguments); + + /// + /// Push multiple APKs to the device and install them. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// s which represents the split APKs to install. + /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// The arguments to pass to adb install-create. + public static void InstallMultiple(this IAdbClient client, DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress = null, params string[] arguments) => + client.InstallMultiple(device, splitAPKs, packageName, progress == null ? null : progress.Report, arguments); + + /// + /// Write an apk into the given install session. + /// + /// An instance of a class that implements the interface. + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + public static void InstallWrite(this IAdbClient client, DeviceData device, Stream apk, string apkName, string session, IProgress? progress) => + client.InstallWrite(device, apk, apkName, session, progress == null ? null : progress.Report); +#endif } } diff --git a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs index 08d11360..232d56f2 100644 --- a/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs +++ b/AdvancedSharpAdbClient/Extensions/EnumerableBuilder.cs @@ -4,7 +4,10 @@ // using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; +using System.IO; namespace AdvancedSharpAdbClient { @@ -46,6 +49,241 @@ public static FileStatistics FileStatisticsCreator(ReadOnlySpan values) }; int ReadInt32(in ReadOnlySpan data) => data[index++] | (data[index++] << 8) | (data[index++] << 16) | (data[index++] << 24); } + + /// + /// Build a class. + /// + /// The data that feeds the struct. + /// A new instance of struct. + public static LogEntry? LogEntryCreator(ReadOnlySpan values) + { + if (values.IsEmpty) { return null; } + int index = 0; + + // Read the log data in binary format. This format is defined at + // https://android.googlesource.com/platform/system/logging/+/refs/heads/main/liblog/include/log/log_read.h#39 + ushort? payloadLengthValue = ReadUInt16(in values); + ushort? headerSizeValue = payloadLengthValue == null ? null : ReadUInt16(in values); + int? pidValue = headerSizeValue == null ? null : ReadInt32(in values); + uint? tidValue = pidValue == null ? null : ReadUInt32(in values); + uint? secValue = tidValue == null ? null : ReadUInt32(in values); + uint? nsecValue = secValue == null ? null : ReadUInt32(in values); + + if (nsecValue == null) + { + return null; + } + + ushort payloadLength = payloadLengthValue!.Value; + ushort headerSize = headerSizeValue!.Value; + int pid = pidValue!.Value; + uint tid = tidValue!.Value; + uint sec = secValue!.Value; + uint 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 + LogId id = 0; + uint uid = 0; + + if (headerSize != 0) + { + if (headerSize >= 0x18) + { + uint? idValue = ReadUInt32(in values); + + if (idValue == null) + { + return null; + } + + uid = idValue.Value; + id = (LogId)uid; + } + + if (headerSize >= 0x1c) + { + uint? uidValue = ReadUInt32(in values); + + if (uidValue == null) + { + return null; + } + + uid = uidValue.Value; + } + + if (headerSize > 0x20) + { + if (headerSize == 0x20) + { + // Not sure what this is. + _ = ReadUInt32(in values); + } + else + { + throw new ArgumentOutOfRangeException(nameof(values), $"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"); + } + } + } + + ReadOnlySpan data = ReadBytesSafe(in values, payloadLength); + + if (data.IsEmpty) + { + return null; + } + + DateTimeOffset timestamp = DateTimeExtensions.FromUnixTimeSeconds(sec); + + switch (id) + { + case >= LogId.Min and <= LogId.Max and not LogId.Events: + // 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 = AdbClient.Encoding.GetString(data[1..tagEnd]); + string message = AdbClient.Encoding.GetString(data.Slice(tagEnd + 1, data.Length - tagEnd - 2)); + + return new AndroidLogEntry + { + Data = data.ToArray(), + PayloadLength = payloadLength, + HeaderSize = headerSize, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + NanoSeconds = nsec, + Id = id, + Uid = uid, + Priority = (Priority)priority, + Message = message, + Tag = tag + }; + + case LogId.Events: + byte[] dataArray = data.ToArray(); + + // https://android.googlesource.com/platform/system/core.git/+/master/liblog/logprint.c#547 + EventLogEntry entry = new() + { + Data = dataArray, + PayloadLength = payloadLength, + HeaderSize = headerSize, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + NanoSeconds = nsec, + Id = id, + Uid = uid + }; + + // 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(dataArray)) + { + using BinaryReader reader = new(dataStream); + _ = reader.ReadInt32(); + + while (dataStream.Position < dataStream.Length) + { + ReadLogEntry(reader, entry.Values); + } + } + + return entry; + + default: + return new LogEntry + { + Data = data.ToArray(), + PayloadLength = payloadLength, + HeaderSize = headerSize, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + NanoSeconds = nsec, + Id = id, + Uid = uid + }; + } + + static void ReadLogEntry(BinaryReader reader, ICollection parent) + { + EventLogType type = (EventLogType)reader.ReadByte(); + + switch (type) + { + case EventLogType.Integer: + parent.Add(reader.ReadInt32()); + break; + + case EventLogType.Long: + parent.Add(reader.ReadInt64()); + break; + + case EventLogType.String: + int stringLength = reader.ReadInt32(); + byte[] messageData = reader.ReadBytes(stringLength); + string message = AdbClient.Encoding.GetString(messageData); + parent.Add(message); + break; + + case EventLogType.List: + byte listLength = reader.ReadByte(); + List list = new(listLength); + for (int i = 0; i < listLength; i++) + { + ReadLogEntry(reader, list); + } + parent.Add(list); + break; + + case EventLogType.Float: + parent.Add(reader.ReadSingle()); + break; + } + } + + ushort? ReadUInt16(in ReadOnlySpan bytes) + { + ReadOnlySpan data = ReadBytesSafe(bytes, 2); + return data == null ? null : (ushort)(data[0] | (data[1] << 8)); + } + + uint? ReadUInt32(in ReadOnlySpan bytes) + { + ReadOnlySpan data = ReadBytesSafe(bytes, 4); + return data == null ? null : (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); + } + + int? ReadInt32(in ReadOnlySpan bytes) + { + ReadOnlySpan data = ReadBytesSafe(bytes, 4); + return data.Length != 4 ? null : data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + } + + ReadOnlySpan ReadBytesSafe(in ReadOnlySpan bytes, int count) + { + if (bytes.Length < index + count) { return null; } + ReadOnlySpan data = bytes.Slice(index, count); + index += count; + return data; + } + } } } #endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Extensions/Extensions.cs b/AdvancedSharpAdbClient/Extensions/Extensions.cs index 65d5c1fc..41ac2a08 100644 --- a/AdvancedSharpAdbClient/Extensions/Extensions.cs +++ b/AdvancedSharpAdbClient/Extensions/Extensions.cs @@ -3,12 +3,10 @@ // using System; -using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Net; using System.Net.Sockets; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs new file mode 100644 index 00000000..41a43c26 --- /dev/null +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.Async.cs @@ -0,0 +1,69 @@ +#if (HAS_TASK && !NETFRAMEWORK) || NET40_OR_GREATER +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.IO; +using System.Threading; + +namespace AdvancedSharpAdbClient +{ + public static partial class SyncServiceExtensions + { + /// + /// Asynchronously pushes (uploads) a file to the remote device. + /// + /// An instance of a class that implements the interface. + /// A that contains the contents of the file. + /// The path, on the device, to which to push 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 parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + public static Task PushAsync(this ISyncService service, Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress = null, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, permissions, timestamp, progress == null ? null : progress.Report, cancellationToken); + + /// + /// Asynchronously pulls (downloads) a file from the remote device. + /// + /// An instance of a class that implements the interface. + /// The path, on the device, of the file to pull. + /// A that will receive the contents of the file. + /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + public static Task PullAsync(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, CancellationToken cancellationToken = default) => + service.PullAsync(remotePath, stream, progress == null ? null : progress.Report, cancellationToken); + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + /// Asynchronously pushes (uploads) a file to the remote device. + /// + /// An instance of a class that implements the interface. + /// A that contains the contents of the file. + /// The path, on the device, to which to push 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 parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + public static Task PushAsync(this ISyncService.IWinRT service, IInputStream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress = null, CancellationToken cancellationToken = default) => + service.PushAsync(stream, remotePath, permissions, timestamp, progress == null ? null : progress.Report, cancellationToken); + + /// + /// Asynchronously pulls (downloads) a file from the remote device. + /// + /// An instance of a class that implements the interface. + /// The path, on the device, of the file to pull. + /// A that will receive the contents of the file. + /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + public static Task PullAsync(this ISyncService.IWinRT service, string remotePath, IOutputStream stream, IProgress? progress = null, CancellationToken cancellationToken = default) => + service.PullAsync(remotePath, stream, progress == null ? null : progress.Report, cancellationToken); +#endif + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs new file mode 100644 index 00000000..37dc2945 --- /dev/null +++ b/AdvancedSharpAdbClient/Extensions/SyncServiceExtensions.cs @@ -0,0 +1,41 @@ +#if !NETFRAMEWORK || NET40_OR_GREATER +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; +using System.IO; + +namespace AdvancedSharpAdbClient +{ + /// + /// Provides extension methods for the interface. Provides overloads for commonly used functions. + /// + public static partial class SyncServiceExtensions + { + /// + /// Pushes (uploads) a file to the remote device. + /// + /// An instance of a class that implements the interface. + /// A that contains the contents of the file. + /// The path, on the device, to which to push 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 parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + public static void Push(this ISyncService service, Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress = null, in bool isCancelled = false) => + service.Push(stream, remotePath, permissions, timestamp, progress == null ? null : progress.Report, isCancelled); + + /// + /// Pulls (downloads) a file from the remote device. + /// + /// An instance of a class that implements the interface. + /// The path, on the device, of the file to pull. + /// A that will receive the contents of the file. + /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + public static void Pull(this ISyncService service, string remotePath, Stream stream, IProgress? progress = null, in bool isCancelled = false) => + service.Pull(remotePath, stream, progress == null ? null : progress.Report, isCancelled); + } +} +#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs index 53892788..3066fa34 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.Async.cs @@ -311,7 +311,7 @@ public partial interface IAdbClient /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install. /// A which represents the asynchronous operation. - Task InstallAsync(DeviceData device, Stream apk, IProgress? progress, CancellationToken cancellationToken, params string[] arguments); + Task InstallAsync(DeviceData device, Stream apk, Action? progress, CancellationToken cancellationToken, params string[] arguments); /// /// Asynchronously push multiple APKs to the device and install them. @@ -324,7 +324,7 @@ public partial interface IAdbClient /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install-create. /// A which represents the asynchronous operation. - Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress, CancellationToken cancellationToken, params string[] arguments); + Task InstallMultipleAsync(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, Action? progress, CancellationToken cancellationToken, params string[] arguments); /// /// Asynchronously push multiple APKs to the device and install them. @@ -337,7 +337,7 @@ public partial interface IAdbClient /// A which can be used to cancel the asynchronous operation. /// The arguments to pass to adb install-create. /// A which represents the asynchronous operation. - Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress, CancellationToken cancellationToken, params string[] arguments); + Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, Action? progress, CancellationToken cancellationToken, params string[] arguments); /// /// Like "install", but starts an install session asynchronously. @@ -360,7 +360,7 @@ public partial interface IAdbClient /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. - Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, IProgress? progress, CancellationToken cancellationToken); + Task InstallWriteAsync(DeviceData device, Stream apk, string apkName, string session, Action? progress, CancellationToken cancellationToken); /// /// Asynchronously commit the given active install session, installing the app. @@ -389,6 +389,64 @@ public partial interface IAdbClient /// A which returns the list of all features supported by the current device. Task> GetFeatureSetAsync(DeviceData device, CancellationToken cancellationToken); +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + /// Provides access to the WinRT specific methods of the interface. + /// + public interface IWinRT + { + /// + /// Asynchronously installs an Android application on an device. + /// + /// The device on which to install the application. + /// A which represents the application to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install. + /// A which represents the asynchronous operation. + Task InstallAsync(DeviceData device, IRandomAccessStream apk, Action? progress, CancellationToken cancellationToken, params string[] arguments); + + /// + /// Asynchronously push multiple APKs to the device and install them. + /// + /// The device on which to install the application. + /// A which represents the base APK to install. + /// s which represents the split APKs to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install-create. + /// A which represents the asynchronous operation. + Task InstallMultipleAsync(DeviceData device, IRandomAccessStream baseAPK, IEnumerable splitAPKs, Action? progress, CancellationToken cancellationToken, params string[] arguments); + + /// + /// Asynchronously push multiple APKs to the device and install them. + /// + /// The device on which to install the application. + /// s which represents the split APKs to install. + /// The package name of the base APK to install. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as , representing the state of installation. + /// A which can be used to cancel the asynchronous operation. + /// The arguments to pass to adb install-create. + /// A which represents the asynchronous operation. + Task InstallMultipleAsync(DeviceData device, IEnumerable splitAPKs, string packageName, Action? progress, CancellationToken cancellationToken, params string[] arguments); + + /// + /// Asynchronously write an apk into the given install session. + /// + /// The device on which to install the application. + /// A which represents the application to install. + /// The name of the application. + /// The session ID of the install session. + /// An optional parameter which, when specified, returns progress notifications. + /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. + /// A which can be used to cancel the asynchronous operation. + /// A which represents the asynchronous operation. + Task InstallWriteAsync(DeviceData device, IRandomAccessStream apk, string apkName, string session, Action? progress, CancellationToken cancellationToken); + } +#endif } } #endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs index 81600a3a..66305fdc 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbClient.cs @@ -264,8 +264,9 @@ public partial interface IAdbClient /// /// The device on which to run the event log service. /// A callback which will receive the event log messages as they are received. + /// A that can be used to cancel the task. /// Optionally, the names of the logs to receive. - void RunLogService(DeviceData device, Action messageSink, params LogId[] logNames); + void RunLogService(DeviceData device, Action messageSink, in bool isCancelled, params LogId[] logNames); /// /// Reboots the specified device in to the specified mode. @@ -316,7 +317,7 @@ public partial interface IAdbClient /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install. - void Install(DeviceData device, Stream apk, IProgress? progress, params string[] arguments); + void Install(DeviceData device, Stream apk, Action? progress, params string[] arguments); /// /// Push multiple APKs to the device and install them. @@ -327,7 +328,7 @@ public partial interface IAdbClient /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install-create. - void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, IProgress? progress, params string[] arguments); + void InstallMultiple(DeviceData device, Stream baseAPK, IEnumerable splitAPKs, Action? progress, params string[] arguments); /// /// Push multiple APKs to the device and install them. @@ -338,7 +339,7 @@ public partial interface IAdbClient /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as , representing the state of installation. /// The arguments to pass to adb install-create. - void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, IProgress? progress, params string[] arguments); + void InstallMultiple(DeviceData device, IEnumerable splitAPKs, string packageName, Action? progress, params string[] arguments); /// /// Like "install", but starts an install session. @@ -358,7 +359,7 @@ public partial interface IAdbClient /// The session ID of the install session. /// An optional parameter which, when specified, returns progress notifications. /// The progress is reported as a value between 0 and 100, representing the percentage of the apk which has been transferred. - void InstallWrite(DeviceData device, Stream apk, string apkName, string session, IProgress? progress); + void InstallWrite(DeviceData device, Stream apk, string apkName, string session, Action? progress); /// /// Commit the given active install session, installing the app. diff --git a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs index e9082641..9b1fcff8 100644 --- a/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/IAdbSocket.Async.cs @@ -149,7 +149,7 @@ public partial interface IAdbSocket /// An array of type Byte that is the storage location for the received data. /// A that can be used to cancel the task. /// Cancelling the task will also close the socket. - /// A that represents the asynchronous operation. The result value of the task contains the number of bytes received. + /// A that represents the asynchronous operation. The result value of the task contains the number of bytes received. public ValueTask ReadAsync(Memory data, CancellationToken cancellationToken) { byte[] bytes = new byte[data.Length]; diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs index 4987d897..122a3f09 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.Async.cs @@ -23,7 +23,7 @@ public partial interface ISyncService /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress, CancellationToken cancellationToken); + Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action? progress, CancellationToken cancellationToken); /// /// Asynchronously pulls (downloads) a file from the remote device. @@ -33,7 +33,7 @@ public partial interface ISyncService /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. /// A that can be used to cancel the task. /// A which represents the asynchronous operation. - Task PullAsync(string remotePath, Stream stream, IProgress? progress, CancellationToken cancellationToken); + Task PullAsync(string remotePath, Stream stream, Action? progress, CancellationToken cancellationToken); /// /// Asynchronously returns information about a file on the device. @@ -80,6 +80,36 @@ async IAsyncEnumerable GetDirectoryAsyncListing(string remotePat /// A which can be used to cancel the asynchronous operation. /// A which represents the asynchronous operation. Task ReopenAsync(CancellationToken cancellationToken = default); + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + /// Provides access to the WinRT specific methods of the interface. + /// + public interface IWinRT + { + /// + /// Asynchronously pushes (uploads) a file to the remote device. + /// + /// A that contains the contents of the file. + /// The path, on the device, to which to push the file. + /// 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 parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + Task PushAsync(IInputStream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action? progress, CancellationToken cancellationToken); + + /// + /// Asynchronously pulls (downloads) a file from the remote device. + /// + /// The path, on the device, of the file to pull. + /// A that will receive the contents of the file. + /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. + /// A that can be used to cancel the task. + /// A which represents the asynchronous operation. + Task PullAsync(string remotePath, IOutputStream stream, Action? progress, CancellationToken cancellationToken); + } +#endif } } #endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs index b718df56..915c2698 100644 --- a/AdvancedSharpAdbClient/Interfaces/ISyncService.cs +++ b/AdvancedSharpAdbClient/Interfaces/ISyncService.cs @@ -30,7 +30,7 @@ public partial interface ISyncService : IDisposable /// The time at which the file was last modified. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. /// A that can be used to cancel the task. - void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress, in bool isCancelled); + void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action? progress, in bool isCancelled); /// /// Pulls (downloads) a file from the remote device. @@ -39,7 +39,7 @@ public partial interface ISyncService : IDisposable /// A that will receive the contents of the file. /// An optional parameter which, when specified, returns progress notifications. The progress is reported as , representing the state of the file which has been transferred. /// A that can be used to cancel the task. - void Pull(string remotePath, Stream stream, IProgress? progress, in bool isCancelled); + void Pull(string remotePath, Stream stream, Action? progress, in bool isCancelled); /// /// Returns information about a file on the device. diff --git a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs index 451a5f4c..e0ff3737 100644 --- a/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs +++ b/AdvancedSharpAdbClient/Interfaces/ITcpSocket.Async.cs @@ -36,7 +36,7 @@ public partial interface ITcpSocket /// An array of type Byte that contains the data to be sent. /// 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. + /// A which returns the number of bytes sent to the Socket. Task SendAsync(byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken); /// @@ -47,7 +47,7 @@ public partial interface ITcpSocket /// 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. + /// A which returns the number of bytes sent to the Socket. Task SendAsync(byte[] buffer, int size, SocketFlags socketFlags, CancellationToken cancellationToken); /// @@ -60,7 +60,7 @@ public partial interface ITcpSocket /// 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. + /// A which returns the number of bytes sent to the Socket. Task SendAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); /// @@ -71,7 +71,7 @@ public partial interface ITcpSocket /// 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. + /// A which returns the number of bytes received. Task ReceiveAsync(byte[] buffer, SocketFlags socketFlags, CancellationToken cancellationToken); /// @@ -83,7 +83,7 @@ public partial interface ITcpSocket /// 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. + /// A which returns the number of bytes received. Task ReceiveAsync(byte[] buffer, int size, SocketFlags socketFlags, CancellationToken cancellationToken); /// @@ -96,7 +96,7 @@ public partial interface ITcpSocket /// 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. + /// A which returns the number of bytes received. Task ReceiveAsync(byte[] buffer, int offset, int size, SocketFlags socketFlags, CancellationToken cancellationToken); #if HAS_BUFFERS @@ -107,7 +107,7 @@ public partial interface ITcpSocket /// An array of type Byte that contains the data to be sent. /// 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. + /// A which returns the number of bytes sent to the Socket. public ValueTask SendAsync(ReadOnlyMemory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) => new(ReceiveAsync(buffer.ToArray(), socketFlags, cancellationToken)); /// @@ -118,7 +118,7 @@ public partial interface ITcpSocket /// 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. + /// A which returns the number of bytes received. public ValueTask ReceiveAsync(Memory buffer, SocketFlags socketFlags, CancellationToken cancellationToken) { byte[] bytes = new byte[buffer.Length]; diff --git a/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs b/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs index 1531cf5a..2b3c55ae 100644 --- a/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs +++ b/AdvancedSharpAdbClient/Logs/AndroidLogEntry.cs @@ -9,7 +9,7 @@ namespace AdvancedSharpAdbClient.Logs /// /// Represents a standard Android log entry (an entry in any Android log buffer except the Event buffer). /// - /// + /// public class AndroidLogEntry : LogEntry { /// @@ -22,7 +22,8 @@ public class AndroidLogEntry : LogEntry { Priority.Info, 'I' }, { Priority.Warn, 'W' }, { Priority.Error, 'E' }, - { Priority.Assert, 'A' } + { Priority.Fatal, 'F' }, + { Priority.Silent, 'S' } }; /// @@ -48,7 +49,7 @@ public AndroidLogEntry() { } /// public override string ToString() => - $"{TimeStamp:yy-MM HH:mm:ss.fff} {ProcessId,5} {ProcessId,5} {FormatPriority(Priority)} {Tag,-8}: {Message}"; + $"{TimeStamp.LocalDateTime:yy-MM-dd HH:mm:ss.fff} {ProcessId,5} {ProcessId,5} {FormatPriority(Priority)} {Tag,-8}: {Message}"; /// /// Converts a value to a char that represents that value in the system log. diff --git a/AdvancedSharpAdbClient/Logs/Enums/EventLogType.cs b/AdvancedSharpAdbClient/Logs/Enums/EventLogType.cs index 19d43585..2fd1f155 100644 --- a/AdvancedSharpAdbClient/Logs/Enums/EventLogType.cs +++ b/AdvancedSharpAdbClient/Logs/Enums/EventLogType.cs @@ -7,12 +7,27 @@ namespace AdvancedSharpAdbClient.Logs /// /// Represents the different types of values that can be stored in an event log entry. /// + /// public enum EventLogType : byte { + /* Special markers for android_log_list_element type */ + + /// + /// The value declare end of list. + /// + ListStop = (byte)'\n', + + /// + /// The value means protocol error. + /// + Unknown = (byte)'?', + + /* must match with declaration in java/android/android/util/EventLog.java */ + /// /// The value is a four-byte signed integer. /// - Integer, + Integer = 0, /// /// The value is an eight-byte signed integer. diff --git a/AdvancedSharpAdbClient/Logs/Enums/LogId.cs b/AdvancedSharpAdbClient/Logs/Enums/LogId.cs index 1f6f829d..5c2d1288 100644 --- a/AdvancedSharpAdbClient/Logs/Enums/LogId.cs +++ b/AdvancedSharpAdbClient/Logs/Enums/LogId.cs @@ -13,7 +13,12 @@ public enum LogId : uint /// /// The main log buffer /// - Main, + Main = 0, + + /// + /// The minimum log id. + /// + Min = Main, /// /// The buffer that contains radio/telephony related messages. @@ -35,9 +40,34 @@ public enum LogId : uint /// Crash, + /// + /// The Android statistics log buffer. + /// + Stats, + + /// + /// The Android security log buffer. + /// + Security, + /// /// The Android kernel log buffer. /// - Kernel + Kernel, + + /// + /// The maximum log id. + /// + Max = Kernel, + + /// + /// Let the logging function choose the best log target. + /// + Default = 0x7FFFFFFF, + + /// + /// All Android log buffers. + /// + All = 0xFFFFFFFF } } diff --git a/AdvancedSharpAdbClient/Logs/Enums/Priority.cs b/AdvancedSharpAdbClient/Logs/Enums/Priority.cs index d650b7ed..60c30d5d 100644 --- a/AdvancedSharpAdbClient/Logs/Enums/Priority.cs +++ b/AdvancedSharpAdbClient/Logs/Enums/Priority.cs @@ -7,37 +7,57 @@ namespace AdvancedSharpAdbClient.Logs /// /// Represents a log priority. /// - /// + /// public enum Priority : byte { + /// + /// For internal use only. + /// + Unknown = 0, + + /// + /// The default priority, for internal use only. + /// + Default, + /// /// Represents a verbose message. /// - Verbose = 2, + Verbose, /// /// Represents a debug message. /// - Debug = 3, + Debug, /// /// Represents an informational message. /// - Info = 4, + Info, /// /// Represents a warning. /// - Warn = 5, + Warn, /// /// Represents an error. /// - Error = 6, + Error, /// /// Represents an assertion which failed. /// - Assert = 7 + Fatal, + + /// + /// Represents an assertion which failed. + /// + Assert = Fatal, + + /// + /// For internal use only. + /// + Silent } } diff --git a/AdvancedSharpAdbClient/Logs/EventLogEntry.cs b/AdvancedSharpAdbClient/Logs/EventLogEntry.cs index 2b7de5ea..2d17fe58 100644 --- a/AdvancedSharpAdbClient/Logs/EventLogEntry.cs +++ b/AdvancedSharpAdbClient/Logs/EventLogEntry.cs @@ -25,6 +25,7 @@ public EventLogEntry() { } /// /// Gets or sets the values of this event log entry. /// + /// Values can be of type , , , or . public List Values { get; set; } = []; } } diff --git a/AdvancedSharpAdbClient/Logs/LogEntry.cs b/AdvancedSharpAdbClient/Logs/LogEntry.cs index 4c232140..bf78d945 100644 --- a/AdvancedSharpAdbClient/Logs/LogEntry.cs +++ b/AdvancedSharpAdbClient/Logs/LogEntry.cs @@ -3,6 +3,8 @@ // using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace AdvancedSharpAdbClient.Logs { @@ -12,6 +14,9 @@ namespace AdvancedSharpAdbClient.Logs /// driver unless an upgrade to a newer ABI version is requested. /// /// +#if HAS_BUFFERS + [CollectionBuilder(typeof(EnumerableBuilder), nameof(EnumerableBuilder.LogEntryCreator))] +#endif public class LogEntry { /// @@ -19,6 +24,16 @@ public class LogEntry /// public LogEntry() { } + /// + /// Gets or sets the length of the payload. + /// + public ushort PayloadLength { get; set; } + + /// + /// Gets or sets the size of the header. + /// + public ushort HeaderSize { get; set; } + /// /// Gets or sets the process ID of the code that generated the log message. /// @@ -27,7 +42,7 @@ public LogEntry() { } /// /// Gets or sets the thread ID of the code that generated the log message. /// - public int ThreadId { get; set; } + public uint ThreadId { get; set; } /// /// Gets or sets the date and time at which the message was logged. @@ -37,17 +52,86 @@ public LogEntry() { } /// /// Gets or sets the nanoseconds at which the message was logged. /// - public int NanoSeconds { get; set; } + public uint NanoSeconds { get; set; } /// /// Gets or sets the log id (v3) of the payload effective UID of logger (v2); /// this value is not available for v1 entries. /// - public uint Id { get; set; } + public LogId Id { get; set; } + + /// + /// Gets or sets the payload effective UID of logger; + /// this value is not available for v1 entries. + /// + public uint Uid { get; set; } /// /// Gets or sets the entry's payload. /// public byte[] Data { get; set; } = []; + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator that can be used to iterate through the . + public IEnumerator GetEnumerator() + { + yield return (byte)PayloadLength; + yield return (byte)(PayloadLength >> 8); + + yield return (byte)HeaderSize; + yield return (byte)(HeaderSize >> 8); + + yield return (byte)ProcessId; + yield return (byte)(ProcessId >> 8); + yield return (byte)(ProcessId >> 16); + yield return (byte)(ProcessId >> 24); + + yield return (byte)ThreadId; + yield return (byte)(ThreadId >> 8); + yield return (byte)(ThreadId >> 16); + yield return (byte)(ThreadId >> 24); + + long time = TimeStamp.ToUnixTimeSeconds(); + yield return (byte)time; + yield return (byte)(time >> 8); + yield return (byte)(time >> 16); + yield return (byte)(time >> 24); + + yield return (byte)NanoSeconds; + yield return (byte)(NanoSeconds >> 8); + yield return (byte)(NanoSeconds >> 16); + yield return (byte)(NanoSeconds >> 24); + + if (HeaderSize >= 0x18) + { + yield return (byte)Id; + yield return (byte)((uint)Id >> 8); + yield return (byte)((uint)Id >> 16); + yield return (byte)((uint)Id >> 24); + } + + if (HeaderSize >= 0x1c) + { + yield return (byte)Uid; + yield return (byte)(Uid >> 8); + yield return (byte)(Uid >> 16); + yield return (byte)(Uid >> 24); + } + + if (HeaderSize == 0x20) + { + yield return 0; + yield return 0; + yield return 0; + yield return 0; + } + + foreach (byte data in Data) + { + yield return data; + } + } } } diff --git a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs index 8ceb09a8..c7d73221 100644 --- a/AdvancedSharpAdbClient/Logs/LogReader.Async.cs +++ b/AdvancedSharpAdbClient/Logs/LogReader.Async.cs @@ -4,8 +4,8 @@ // using System; +using System.Diagnostics; using System.IO; -using System.Text; using System.Threading; namespace AdvancedSharpAdbClient.Logs @@ -20,14 +20,13 @@ public partial class LogReader 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 + // https://android.googlesource.com/platform/system/logging/+/refs/heads/main/liblog/include/log/log_read.h#39 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); + uint? tidValue = pidValue == null ? null : await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + uint? secValue = tidValue == null ? null : await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + uint? nsecValue = secValue == null ? null : await ReadUInt32Async(cancellationToken).ConfigureAwait(false); if (nsecValue == null) { @@ -37,15 +36,15 @@ public partial class LogReader ushort payloadLength = payloadLengthValue!.Value; ushort headerSize = headerSizeValue!.Value; int pid = pidValue!.Value; - int tid = tidValue!.Value; - int sec = secValue!.Value; - int nsec = nsecValue.Value; + uint tid = tidValue!.Value; + uint sec = secValue!.Value; + uint 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; + LogId id = 0; uint uid = 0; if (headerSize != 0) @@ -59,7 +58,8 @@ public partial class LogReader return null; } - id = idValue.Value; + uid = idValue.Value; + id = (LogId)uid; } if (headerSize >= 0x1c) @@ -76,13 +76,16 @@ public partial class LogReader 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"); + if (headerSize == 0x20) + { + // Not sure what this is. + _ = await ReadUInt32Async(cancellationToken).ConfigureAwait(false); + } + else + { + Debug.WriteLine($"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"); + return null; + } } } @@ -95,54 +98,54 @@ public partial class LogReader DateTimeOffset timestamp = DateTimeExtensions.FromUnixTimeSeconds(sec); - switch ((LogId)id) + switch (id) { - case LogId.Crash - or LogId.Kernel - or LogId.Main - or LogId.Radio - or LogId.System: - { - // format: \0\0 - byte priority = data[0]; + case >= LogId.Min and <= LogId.Max and not LogId.Events: + // 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; + // 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++; - } + 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); + // Message should be null terminated, so remove the last entry, too (-2 instead of -1) + string tag = AdbClient.Encoding.GetString(data, 1, tagEnd - 1); + string message = AdbClient.Encoding.GetString(data, tagEnd + 1, data.Length - tagEnd - 2); - return new AndroidLogEntry - { - Data = data, - ProcessId = pid, - ThreadId = tid, - TimeStamp = timestamp, - NanoSeconds = nsec, - Id = id, - Priority = (Priority)priority, - Message = message, - Tag = tag - }; - } + return new AndroidLogEntry + { + Data = data, + PayloadLength = payloadLength, + HeaderSize = headerSize, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + NanoSeconds = nsec, + Id = id, + Uid = uid, + 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, + PayloadLength = payloadLength, + HeaderSize = headerSize, ProcessId = pid, ThreadId = tid, TimeStamp = timestamp, NanoSeconds = nsec, - Id = id + Id = id, + Uid = uid }; // Use a stream on the data buffer. This will make sure that, @@ -154,7 +157,7 @@ or LogId.Radio using (MemoryStream dataStream = new(data)) { using BinaryReader reader = new(dataStream); - int priority = reader.ReadInt32(); + _ = reader.ReadInt32(); while (dataStream.Position < dataStream.Length) { @@ -168,11 +171,14 @@ or LogId.Radio return new LogEntry { Data = data, + PayloadLength = payloadLength, + HeaderSize = headerSize, ProcessId = pid, ThreadId = tid, TimeStamp = timestamp, NanoSeconds = nsec, - Id = id + Id = id, + Uid = uid }; } } @@ -185,7 +191,7 @@ or LogId.Radio private async Task ReadUInt16Async(CancellationToken cancellationToken = default) { byte[]? data = await ReadBytesSafeAsync(2, cancellationToken).ConfigureAwait(false); - return data == null ? null : BitConverter.ToUInt16(data, 0); + return data == null ? null : (ushort)(data[0] | (data[1] << 8)); } /// @@ -196,7 +202,7 @@ or LogId.Radio private async Task ReadUInt32Async(CancellationToken cancellationToken = default) { byte[]? data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); - return data == null ? null : BitConverter.ToUInt32(data, 0); + return data == null ? null : (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } /// @@ -207,7 +213,7 @@ or LogId.Radio private async Task ReadInt32Async(CancellationToken cancellationToken = default) { byte[]? data = await ReadBytesSafeAsync(4, cancellationToken).ConfigureAwait(false); - return data == null ? null : BitConverter.ToInt32(data, 0); + return data == null ? null : data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } /// diff --git a/AdvancedSharpAdbClient/Logs/LogReader.cs b/AdvancedSharpAdbClient/Logs/LogReader.cs index 120b263a..645d5a1d 100644 --- a/AdvancedSharpAdbClient/Logs/LogReader.cs +++ b/AdvancedSharpAdbClient/Logs/LogReader.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; -using System.Text; namespace AdvancedSharpAdbClient.Logs { @@ -27,14 +27,13 @@ public partial class LogReader(Stream stream) public virtual LogEntry? ReadEntry() { // 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 + // https://android.googlesource.com/platform/system/logging/+/refs/heads/main/liblog/include/log/log_read.h#39 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(); + uint? tidValue = pidValue == null ? null : ReadUInt32(); + uint? secValue = tidValue == null ? null : ReadUInt32(); + uint? nsecValue = secValue == null ? null : ReadUInt32(); if (nsecValue == null) { @@ -44,15 +43,16 @@ public partial class LogReader(Stream stream) ushort payloadLength = payloadLengthValue!.Value; ushort headerSize = headerSizeValue!.Value; int pid = pidValue!.Value; - int tid = tidValue!.Value; - int sec = secValue!.Value; - int nsec = nsecValue.Value; + uint tid = tidValue!.Value; + uint sec = secValue!.Value; + uint 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; + LogId id = 0; + uint uid = 0; if (headerSize != 0) { @@ -65,7 +65,8 @@ public partial class LogReader(Stream stream) return null; } - id = idValue.Value; + uid = idValue.Value; + id = (LogId)uid; } if (headerSize >= 0x1c) @@ -77,18 +78,21 @@ public partial class LogReader(Stream stream) return null; } - _ = uidValue.Value; - } - - if (headerSize >= 0x20) - { - // Not sure what this is. - _ = ReadUInt32(); + uid = uidValue.Value; } 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"); + if (headerSize == 0x20) + { + // Not sure what this is. + _ = ReadUInt32(); + } + else + { + Debug.WriteLine($"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"); + return null; + } } } @@ -101,54 +105,54 @@ public partial class LogReader(Stream stream) DateTimeOffset timestamp = DateTimeExtensions.FromUnixTimeSeconds(sec); - switch ((LogId)id) + switch (id) { - case LogId.Crash - or LogId.Kernel - or LogId.Main - or LogId.Radio - or LogId.System: - { - // format: \0\0 - byte priority = data[0]; + case >= LogId.Min and <= LogId.Max and not LogId.Events: + // 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; + // 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++; - } + 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); + // Message should be null terminated, so remove the last entry, too (-2 instead of -1) + string tag = AdbClient.Encoding.GetString(data, 1, tagEnd - 1); + string message = AdbClient.Encoding.GetString(data, tagEnd + 1, data.Length - tagEnd - 2); - return new AndroidLogEntry - { - Data = data, - ProcessId = pid, - ThreadId = tid, - TimeStamp = timestamp, - NanoSeconds = nsec, - Id = id, - Priority = (Priority)priority, - Message = message, - Tag = tag - }; - } + return new AndroidLogEntry + { + Data = data, + PayloadLength = payloadLength, + HeaderSize = headerSize, + ProcessId = pid, + ThreadId = tid, + TimeStamp = timestamp, + NanoSeconds = nsec, + Id = id, + Uid = uid, + 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, + PayloadLength = payloadLength, + HeaderSize = headerSize, ProcessId = pid, ThreadId = tid, TimeStamp = timestamp, NanoSeconds = nsec, - Id = id + Id = id, + Uid = uid }; // Use a stream on the data buffer. This will make sure that, @@ -157,7 +161,7 @@ or LogId.Radio using (MemoryStream dataStream = new(data)) { using BinaryReader reader = new(dataStream); - int priority = reader.ReadInt32(); + _ = reader.ReadInt32(); while (dataStream.Position < dataStream.Length) { @@ -171,11 +175,14 @@ or LogId.Radio return new LogEntry { Data = data, + PayloadLength = payloadLength, + HeaderSize = headerSize, ProcessId = pid, ThreadId = tid, TimeStamp = timestamp, NanoSeconds = nsec, - Id = id + Id = id, + Uid = uid }; } } @@ -183,16 +190,12 @@ or LogId.Radio /// /// Reads a single log entry from the stream. /// - protected void ReadLogEntry(BinaryReader reader, ICollection parent) + protected static void ReadLogEntry(BinaryReader reader, ICollection parent) { EventLogType type = (EventLogType)reader.ReadByte(); switch (type) { - case EventLogType.Float: - parent.Add(reader.ReadSingle()); - break; - case EventLogType.Integer: parent.Add(reader.ReadInt32()); break; @@ -201,9 +204,16 @@ protected void ReadLogEntry(BinaryReader reader, ICollection parent) parent.Add(reader.ReadInt64()); break; + case EventLogType.String: + int stringLength = reader.ReadInt32(); + byte[] messageData = reader.ReadBytes(stringLength); + string message = AdbClient.Encoding.GetString(messageData); + parent.Add(message); + break; + case EventLogType.List: byte listLength = reader.ReadByte(); - List list = []; + List list = new(listLength); for (int i = 0; i < listLength; i++) { ReadLogEntry(reader, list); @@ -211,11 +221,8 @@ protected void ReadLogEntry(BinaryReader reader, ICollection parent) parent.Add(list); break; - case EventLogType.String: - int stringLength = reader.ReadInt32(); - byte[] messageData = reader.ReadBytes(stringLength); - string message = Encoding.ASCII.GetString(messageData); - parent.Add(message); + case EventLogType.Float: + parent.Add(reader.ReadSingle()); break; } } @@ -227,13 +234,7 @@ protected void ReadLogEntry(BinaryReader reader, ICollection parent) protected ushort? ReadUInt16() { byte[]? data = ReadBytesSafe(2); - - return data == null ? null -#if HAS_BUFFERS - : BitConverter.ToUInt16(data); -#else - : BitConverter.ToUInt16(data, 0); -#endif + return data == null ? null : (ushort)(data[0] | (data[1] << 8)); } /// @@ -243,13 +244,7 @@ protected void ReadLogEntry(BinaryReader reader, ICollection parent) protected uint? ReadUInt32() { byte[]? data = ReadBytesSafe(4); - - return data == null ? null -#if HAS_BUFFERS - : BitConverter.ToUInt32(data); -#else - : BitConverter.ToUInt32(data, 0); -#endif + return data == null ? null : (uint)(data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)); } /// @@ -259,13 +254,7 @@ protected void ReadLogEntry(BinaryReader reader, ICollection parent) protected int? ReadInt32() { byte[]? data = ReadBytesSafe(4); - - return data == null ? null -#if HAS_BUFFERS - : BitConverter.ToInt32(data); -#else - : BitConverter.ToInt32(data, 0); -#endif + return data == null ? null : data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); } /// @@ -280,7 +269,7 @@ protected void ReadLogEntry(BinaryReader reader, ICollection parent) int read; #if HAS_BUFFERS - while ((read = stream.Read(data.AsSpan(totalRead, count - totalRead))) > 0) + while ((read = stream.Read(data.AsSpan(totalRead))) > 0) #else while ((read = stream.Read(data, totalRead, count - totalRead)) > 0) #endif diff --git a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs index c43f34da..445c5946 100644 --- a/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs +++ b/AdvancedSharpAdbClient/Polyfills/Extensions/EnumerableExtensions.cs @@ -1,4 +1,8 @@ -using System; +// +// Copyright (c) The Android Open Source Project, Ryan Conrad, Quamotion, yungd1plomat, wherewhere. All rights reserved. +// + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; diff --git a/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs b/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs deleted file mode 100644 index e92586af..00000000 --- a/AdvancedSharpAdbClient/Polyfills/Interfaces/IProgress.cs +++ /dev/null @@ -1,19 +0,0 @@ -#if NETFRAMEWORK && !NET40_OR_GREATER -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel; - -namespace AdvancedSharpAdbClient.Polyfills -{ - /// Defines a provider for progress updates. - /// The type of progress update value. - [EditorBrowsable(EditorBrowsableState.Never)] - public interface IProgress - { - /// Reports a progress update. - /// The value of the updated progress. - void Report(T value); - } -} -#endif \ No newline at end of file diff --git a/AdvancedSharpAdbClient/Properties/GlobalUsings.cs b/AdvancedSharpAdbClient/Properties/GlobalUsings.cs index 3f278b5f..0b064417 100644 --- a/AdvancedSharpAdbClient/Properties/GlobalUsings.cs +++ b/AdvancedSharpAdbClient/Properties/GlobalUsings.cs @@ -17,6 +17,7 @@ #endif #if WINDOWS_UWP +global using System.Runtime.InteropServices.WindowsRuntime; global using Windows.ApplicationModel; global using Windows.Foundation; global using Windows.Foundation.Metadata; @@ -37,7 +38,9 @@ #endif #if WINDOWS10_0_17763_0_OR_GREATER +global using System.Runtime.InteropServices.WindowsRuntime; global using Windows.ApplicationModel; +global using Windows.Storage.Streams; global using Buffer = System.Buffer; global using DateTime = System.DateTime; global using Point = System.Drawing.Point; diff --git a/AdvancedSharpAdbClient/SyncService.Async.cs b/AdvancedSharpAdbClient/SyncService.Async.cs index e94886c3..a7270642 100644 --- a/AdvancedSharpAdbClient/SyncService.Async.cs +++ b/AdvancedSharpAdbClient/SyncService.Async.cs @@ -31,7 +31,7 @@ public virtual async Task ReopenAsync(CancellationToken cancellationToken = defa } /// - public virtual async Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress = null, CancellationToken cancellationToken = default) + public virtual async Task PushAsync(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action? progress = null, CancellationToken cancellationToken = default) { ExceptionExtensions.ThrowIfNull(stream); ExceptionExtensions.ThrowIfNull(remotePath); @@ -94,7 +94,7 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi await Socket.SendAsync(buffer, startPosition, read + dataBytes.Length + lengthBytes.Length, cancellationToken).ConfigureAwait(false); #endif // Let the caller know about our progress, if requested - progress?.Report(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); + progress?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); // check if we're canceled cancellationToken.ThrowIfCancellationRequested(); @@ -119,19 +119,19 @@ await stream.ReadAsync(buffer, headerSize, maxDataSize, cancellationToken).Confi } /// - public virtual async Task PullAsync(string remoteFilePath, Stream stream, IProgress? progress = null, CancellationToken cancellationToken = default) + public virtual async Task PullAsync(string remotePath, Stream stream, Action? progress = null, CancellationToken cancellationToken = default) { - ExceptionExtensions.ThrowIfNull(remoteFilePath); + ExceptionExtensions.ThrowIfNull(remotePath); ExceptionExtensions.ThrowIfNull(stream); // Gets file information, including the file size, used to calculate the total amount of bytes to receive. - FileStatistics stat = await StatAsync(remoteFilePath, cancellationToken).ConfigureAwait(false); + FileStatistics stat = await StatAsync(remotePath, cancellationToken).ConfigureAwait(false); long totalBytesToProcess = stat.Size; long totalBytesRead = 0; byte[] buffer = new byte[MaxBufferSize]; - await Socket.SendSyncRequestAsync(SyncCommand.RECV, remoteFilePath, cancellationToken).ConfigureAwait(false); + await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); while (true) { @@ -143,7 +143,7 @@ public virtual async Task PullAsync(string remoteFilePath, Stream stream, IProgr goto finish; case SyncCommand.FAIL: string message = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); - throw new AdbException($"Failed to pull '{remoteFilePath}'. {message}"); + throw new AdbException($"Failed to pull '{remotePath}'. {message}"); case not SyncCommand.DATA: throw new AdbException($"The server sent an invalid response {response}"); } @@ -152,17 +152,165 @@ public virtual async Task PullAsync(string remoteFilePath, Stream stream, IProgr byte[] reply = new byte[4]; _ = await Socket.ReadAsync(reply, cancellationToken).ConfigureAwait(false); - if (!BitConverter.IsLittleEndian) + int size = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); + + if (size > MaxBufferSize) { - Array.Reverse(reply); + throw new AdbException($"The adb server is sending {size} bytes of data, which exceeds the maximum chunk size {MaxBufferSize}"); } - int size = + // now read the length we received #if HAS_BUFFERS - BitConverter.ToInt32(reply); + await Socket.ReadAsync(buffer.AsMemory(0, size), cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(buffer.AsMemory(0, size), cancellationToken).ConfigureAwait(false); #else - BitConverter.ToInt32(reply, 0); + await Socket.ReadAsync(buffer, size, cancellationToken).ConfigureAwait(false); + await stream.WriteAsync(buffer, 0, size, cancellationToken).ConfigureAwait(false); #endif + totalBytesRead += size; + + // Let the caller know about our progress, if requested + progress?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); + + // check if we're canceled + cancellationToken.ThrowIfCancellationRequested(); + } + + finish: return; + } + +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + /// + public virtual async Task PushAsync(IInputStream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action? progress = null, CancellationToken cancellationToken = default) + { + ExceptionExtensions.ThrowIfNull(stream); + ExceptionExtensions.ThrowIfNull(remotePath); + + if (remotePath.Length > MaxPathLength) + { + throw new ArgumentOutOfRangeException(nameof(remotePath), $"The remote path {remotePath} exceeds the maximum path size {MaxPathLength}"); + } + + await Socket.SendSyncRequestAsync(SyncCommand.SEND, remotePath, permissions, cancellationToken).ConfigureAwait(false); + + // create the buffer used to read. + // we read max SYNC_DATA_MAX. + byte[] buffer = new byte[MaxBufferSize]; + + // We need 4 bytes of the buffer to send the 'DATA' command, + // and an additional X bytes to inform how much data we are + // sending. + byte[] dataBytes = SyncCommand.DATA.GetBytes(); + byte[] lengthBytes = BitConverter.GetBytes(MaxBufferSize); + int headerSize = dataBytes.Length + lengthBytes.Length; + int reservedHeaderSize = headerSize; + int maxDataSize = MaxBufferSize - reservedHeaderSize; + lengthBytes = BitConverter.GetBytes(maxDataSize); + + // Try to get the total amount of bytes to transfer. This is not always possible, for example, + // for forward-only streams. + ulong totalBytesToProcess = 0; + ulong totalBytesRead = 0; + + try + { + if (stream is IRandomAccessStream random) + { + totalBytesRead = random.Size; + } + } + catch { } + + // look while there is something to read + while (true) + { + // read up to SYNC_DATA_MAX + IBuffer results = await stream.ReadAsync(buffer.AsBuffer(headerSize, maxDataSize), (uint)maxDataSize, InputStreamOptions.None).AsTask(cancellationToken).ConfigureAwait(false); + uint read = results.Length; + totalBytesRead += read; + + if (read == 0) + { + break; + } + else if (read != maxDataSize) + { + // At the end of the line, so we need to recalculate the length of the header + lengthBytes = BitConverter.GetBytes(read); + headerSize = dataBytes.Length + lengthBytes.Length; + } + + int startPosition = reservedHeaderSize - headerSize; + + Buffer.BlockCopy(dataBytes, 0, buffer, startPosition, dataBytes.Length); + Buffer.BlockCopy(lengthBytes, 0, buffer, startPosition + dataBytes.Length, lengthBytes.Length); + + // now send the data to the device +#if HAS_BUFFERS + await Socket.SendAsync(buffer.AsMemory(startPosition, (int)(read + dataBytes.Length + lengthBytes.Length)), cancellationToken).ConfigureAwait(false); +#else + await Socket.SendAsync(buffer, startPosition, read + dataBytes.Length + lengthBytes.Length, cancellationToken).ConfigureAwait(false); +#endif + // Let the caller know about our progress, if requested + progress?.Invoke(new SyncProgressChangedEventArgs((long)totalBytesRead, (long)totalBytesToProcess)); + + // check if we're canceled + cancellationToken.ThrowIfCancellationRequested(); + } + + // create the DONE message + int time = (int)timestamp.ToUnixTimeSeconds(); + await Socket.SendSyncRequestAsync(SyncCommand.DONE, time, cancellationToken).ConfigureAwait(false); + + // read the result, in a byte array containing 2 int + // (id, size) + SyncCommand result = await Socket.ReadSyncResponseAsync(cancellationToken).ConfigureAwait(false); + + switch (result) + { + case SyncCommand.FAIL: + string message = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); + throw new AdbException(message); + case not SyncCommand.OKAY: + throw new AdbException($"The server sent an invalid response {result}"); + } + } + + /// + public virtual async Task PullAsync(string remotePath, IOutputStream stream, Action? progress = null, CancellationToken cancellationToken = default) + { + ExceptionExtensions.ThrowIfNull(remotePath); + ExceptionExtensions.ThrowIfNull(stream); + + // Gets file information, including the file size, used to calculate the total amount of bytes to receive. + FileStatistics stat = await StatAsync(remotePath, cancellationToken).ConfigureAwait(false); + long totalBytesToProcess = stat.Size; + long totalBytesRead = 0; + + byte[] buffer = new byte[MaxBufferSize]; + + await Socket.SendSyncRequestAsync(SyncCommand.RECV, remotePath, cancellationToken).ConfigureAwait(false); + + while (true) + { + SyncCommand response = await Socket.ReadSyncResponseAsync(cancellationToken).ConfigureAwait(false); + + switch (response) + { + case SyncCommand.DONE: + goto finish; + case SyncCommand.FAIL: + string message = await Socket.ReadSyncStringAsync(cancellationToken).ConfigureAwait(false); + throw new AdbException($"Failed to pull '{remotePath}'. {message}"); + case not SyncCommand.DATA: + throw new AdbException($"The server sent an invalid response {response}"); + } + + // The first 4 bytes contain the length of the data packet + byte[] reply = new byte[4]; + _ = await Socket.ReadAsync(reply, cancellationToken).ConfigureAwait(false); + + int size = reply[0] | (reply[1] << 8) | (reply[2] << 16) | (reply[3] << 24); if (size > MaxBufferSize) { @@ -172,15 +320,14 @@ public virtual async Task PullAsync(string remoteFilePath, Stream stream, IProgr // now read the length we received #if HAS_BUFFERS await Socket.ReadAsync(buffer.AsMemory(0, size), cancellationToken).ConfigureAwait(false); - await stream.WriteAsync(buffer.AsMemory(0, size), cancellationToken).ConfigureAwait(false); #else await Socket.ReadAsync(buffer, size, cancellationToken).ConfigureAwait(false); - await stream.WriteAsync(buffer, 0, size, cancellationToken).ConfigureAwait(false); #endif - totalBytesRead += size; + uint write = await stream.WriteAsync(buffer.AsBuffer(0, size)).AsTask(cancellationToken).ConfigureAwait(false); + totalBytesRead += write; // Let the caller know about our progress, if requested - progress?.Report(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); + progress?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); // check if we're canceled cancellationToken.ThrowIfCancellationRequested(); @@ -188,6 +335,7 @@ public virtual async Task PullAsync(string remoteFilePath, Stream stream, IProgr finish: return; } +#endif /// public virtual async Task StatAsync(string remotePath, CancellationToken cancellationToken = default) diff --git a/AdvancedSharpAdbClient/SyncService.cs b/AdvancedSharpAdbClient/SyncService.cs index 7eb00b9f..7e2827ba 100644 --- a/AdvancedSharpAdbClient/SyncService.cs +++ b/AdvancedSharpAdbClient/SyncService.cs @@ -40,6 +40,9 @@ namespace AdvancedSharpAdbClient /// /// public partial class SyncService : ISyncService +#if WINDOWS_UWP || WINDOWS10_0_17763_0_OR_GREATER + , ISyncService.IWinRT +#endif { /// /// The maximum length of a path on the remote device. @@ -124,7 +127,7 @@ public virtual void Reopen() } /// - public virtual void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, IProgress? progress = null, in bool isCancelled = false) + public virtual void Push(Stream stream, string remotePath, int permissions, DateTimeOffset timestamp, Action? progress = null, in bool isCancelled = false) { ExceptionExtensions.ThrowIfNull(stream); ExceptionExtensions.ThrowIfNull(remotePath); @@ -187,7 +190,7 @@ public virtual void Push(Stream stream, string remotePath, int permissions, Date Socket.Send(buffer, startPosition, read + dataBytes.Length + lengthBytes.Length); #endif // Let the caller know about our progress, if requested - progress?.Report(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); + progress?.Invoke(new SyncProgressChangedEventArgs(totalBytesRead, totalBytesToProcess)); } // create the DONE message @@ -209,7 +212,7 @@ public virtual void Push(Stream stream, string remotePath, int permissions, Date } /// - public virtual void Pull(string remoteFilePath, Stream stream, IProgress? progress = null, in bool isCancelled = false) + public virtual void Pull(string remoteFilePath, Stream stream, Action? progress = null, in bool isCancelled = false) { ExceptionExtensions.ThrowIfNull(remoteFilePath); ExceptionExtensions.ThrowIfNull(stream); @@ -242,17 +245,7 @@ public virtual void Pull(string remoteFilePath, Stream stream, IProgress MaxBufferSize) { @@ -270,7 +263,7 @@ public virtual void Pull(string remoteFilePath, Stream stream, IProgress