From 8f9a4269b685cc66b77b4d0237f35d6ac697c45d Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 7 Aug 2026 15:20:09 -0500 Subject: [PATCH 1/3] Wake device before launching Android apps Wake sleeping devices and dismiss non-secure keyguards before starting deployed applications, keeping preparation and launch in one adb shell invocation. Add --no-wake-device as an escape hatch through the existing _AndroidRunExtraArgs passthrough and cover both command modes in device integration tests. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Microsoft.Android.Run/Program.cs | 7 ++++++- .../Tests/InstallAndRunTests.cs | 20 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.Android.Run/Program.cs b/src/Microsoft.Android.Run/Program.cs index e0366f92066..da8485906ef 100644 --- a/src/Microsoft.Android.Run/Program.cs +++ b/src/Microsoft.Android.Run/Program.cs @@ -14,6 +14,7 @@ string? deviceUserId = null; string? instrumentation = null; bool verbose = false; +bool wakeDevice = true; int? logcatPid = null; Process? logcatProcess = null; CancellationTokenSource cts = new (); @@ -72,6 +73,9 @@ async Task RunAsync (string[] args) { "v|verbose", "Enable verbose output for debugging.", v => verbose = v != null }, + { "no-wake-device", + "Do not wake the device or dismiss its keyguard before launching the application.", + v => wakeDevice = v == null }, { "logcat-args=", "Extra {ARGUMENTS} to pass to 'adb logcat' (e.g., 'monodroid-assembly:S' to silence a tag).", v => logcatArgs = v }, @@ -515,7 +519,8 @@ async Task RunAppAsync () async Task StartAppAsync () { var userArg = string.IsNullOrEmpty (deviceUserId) ? "" : $" --user {deviceUserId}"; - var cmdArgs = $"shell am start -S -W{userArg} -n \"{package}/{activity}\""; + var wakeDeviceCommand = wakeDevice ? "input keyevent KEYCODE_WAKEUP && wm dismiss-keyguard && " : ""; + var cmdArgs = $"shell {wakeDeviceCommand}am start -S -W{userArg} -n \"{package}/{activity}\""; var (exitCode, output, error) = await AdbHelper.RunAsync (adbPath, adbTarget, cmdArgs, cts.Token, verbose); if (exitCode != 0) { Console.Error.WriteLine ($"Error: Failed to start app: {error}"); diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 8a8c5355db1..b7b044700cf 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -341,14 +341,15 @@ static int InvokeIntMethod (Java.Lang.Object instance, string methodName) Assert.True (builder.Uninstall (proj), "Project should have uninstalled."); } - [Test] - public void DotNetRunWaitForExit () + [TestCase ("", true)] + [TestCase ("--no-wake-device", false)] + public void DotNetRunWaitForExit (string extraArgs, bool shouldWakeDevice) { const string logcatMessage = "DOTNET_RUN_TEST_MESSAGE_12345"; var proj = new XamarinAndroidApplicationProject (); // Enable verbose output from Microsoft.Android.Run for debugging - proj.SetProperty ("_AndroidRunExtraArgs", "--verbose"); + proj.SetProperty ("_AndroidRunExtraArgs", $"--verbose {extraArgs}"); // Add a Console.WriteLine that will appear in logcat proj.MainActivity = proj.DefaultMainActivity.Replace ( @@ -401,10 +402,21 @@ public void DotNetRunWaitForExit () } // Write the output to a log file for debugging - string logPath = Path.Combine (Root, builder.ProjectDirectory, "dotnet-run-output.log"); + string logPath = Path.Combine (Root, builder.ProjectDirectory, $"dotnet-run-output-wake-{shouldWakeDevice}.log"); File.WriteAllText (logPath, output.ToString ()); TestContext.AddTestAttachment (logPath); + if (shouldWakeDevice) { + StringAssert.Contains ("shell input keyevent KEYCODE_WAKEUP && wm dismiss-keyguard && am start", output.ToString (), + $"`dotnet run` should wake the device and dismiss its keyguard in the same adb shell command used to start the app. See {logPath} for details."); + } else { + StringAssert.Contains ("shell am start", output.ToString (), + $"`dotnet run` should start the app without waking the device or dismissing its keyguard when --no-wake-device is specified. See {logPath} for details."); + StringAssert.DoesNotContain ("KEYCODE_WAKEUP", output.ToString (), + $"`dotnet run` should not wake the device when --no-wake-device is specified. See {logPath} for details."); + StringAssert.DoesNotContain ("dismiss-keyguard", output.ToString (), + $"`dotnet run` should not dismiss the keyguard when --no-wake-device is specified. See {logPath} for details."); + } Assert.IsTrue (foundMessage, $"Expected message '{logcatMessage}' was not found in output. See {logPath} for details."); } From 9054ae892cb28968d15805671970f1aaa466d093 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 7 Aug 2026 15:47:14 -0500 Subject: [PATCH 2/3] Keep app launch independent of device prep Treat wakeup and keyguard dismissal as best effort so unsupported or secure-device behavior cannot prevent am start from running or control its exit status. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Microsoft.Android.Run/Program.cs | 3 ++- tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.Android.Run/Program.cs b/src/Microsoft.Android.Run/Program.cs index da8485906ef..06727724750 100644 --- a/src/Microsoft.Android.Run/Program.cs +++ b/src/Microsoft.Android.Run/Program.cs @@ -519,7 +519,8 @@ async Task RunAppAsync () async Task StartAppAsync () { var userArg = string.IsNullOrEmpty (deviceUserId) ? "" : $" --user {deviceUserId}"; - var wakeDeviceCommand = wakeDevice ? "input keyevent KEYCODE_WAKEUP && wm dismiss-keyguard && " : ""; + // Device preparation is best effort; am start must run and determine the shell exit code. + var wakeDeviceCommand = wakeDevice ? "input keyevent KEYCODE_WAKEUP; wm dismiss-keyguard; " : ""; var cmdArgs = $"shell {wakeDeviceCommand}am start -S -W{userArg} -n \"{package}/{activity}\""; var (exitCode, output, error) = await AdbHelper.RunAsync (adbPath, adbTarget, cmdArgs, cts.Token, verbose); if (exitCode != 0) { diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index b7b044700cf..57cce3621e5 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -407,7 +407,7 @@ public void DotNetRunWaitForExit (string extraArgs, bool shouldWakeDevice) TestContext.AddTestAttachment (logPath); if (shouldWakeDevice) { - StringAssert.Contains ("shell input keyevent KEYCODE_WAKEUP && wm dismiss-keyguard && am start", output.ToString (), + StringAssert.Contains ("shell input keyevent KEYCODE_WAKEUP; wm dismiss-keyguard; am start", output.ToString (), $"`dotnet run` should wake the device and dismiss its keyguard in the same adb shell command used to start the app. See {logPath} for details."); } else { StringAssert.Contains ("shell am start", output.ToString (), From 125e90b0ec51f0a39b86449fed5afa3bd17a58ae Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 10 Aug 2026 12:34:51 -0500 Subject: [PATCH 3/3] Reuse captured dotnet run output Materialize the streamed log buffer once before writing diagnostics and checking launch behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Tests/InstallAndRunTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index 57cce3621e5..57c01d83683 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -402,19 +402,20 @@ public void DotNetRunWaitForExit (string extraArgs, bool shouldWakeDevice) } // Write the output to a log file for debugging + string outputText = output.ToString (); string logPath = Path.Combine (Root, builder.ProjectDirectory, $"dotnet-run-output-wake-{shouldWakeDevice}.log"); - File.WriteAllText (logPath, output.ToString ()); + File.WriteAllText (logPath, outputText); TestContext.AddTestAttachment (logPath); if (shouldWakeDevice) { - StringAssert.Contains ("shell input keyevent KEYCODE_WAKEUP; wm dismiss-keyguard; am start", output.ToString (), + StringAssert.Contains ("shell input keyevent KEYCODE_WAKEUP; wm dismiss-keyguard; am start", outputText, $"`dotnet run` should wake the device and dismiss its keyguard in the same adb shell command used to start the app. See {logPath} for details."); } else { - StringAssert.Contains ("shell am start", output.ToString (), + StringAssert.Contains ("shell am start", outputText, $"`dotnet run` should start the app without waking the device or dismissing its keyguard when --no-wake-device is specified. See {logPath} for details."); - StringAssert.DoesNotContain ("KEYCODE_WAKEUP", output.ToString (), + StringAssert.DoesNotContain ("KEYCODE_WAKEUP", outputText, $"`dotnet run` should not wake the device when --no-wake-device is specified. See {logPath} for details."); - StringAssert.DoesNotContain ("dismiss-keyguard", output.ToString (), + StringAssert.DoesNotContain ("dismiss-keyguard", outputText, $"`dotnet run` should not dismiss the keyguard when --no-wake-device is specified. See {logPath} for details."); } Assert.IsTrue (foundMessage, $"Expected message '{logcatMessage}' was not found in output. See {logPath} for details.");