Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions AndroidSdk.Tests/CommandSettingsValidation_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ public void AvdStartCommandSettingsRequireName()
Assert.Contains("--name", result.Message);
}

[Fact]
public void AvdDeleteCommandSettingsAllowForceWhenNameIsSet()
{
var settings = new AvdDeleteCommandSettings { Name = "Pixel", Force = true };

var result = settings.Validate();

Assert.True(result.Successful);
}

[Fact]
public void AvdStartCommandSettingsAllowDisableAnimationsAndCpuThreshold()
{
var settings = new AvdStartCommandSettings
{
Name = "Pixel",
DisableAnimations = true,
CpuThreshold = 0.5
};

var result = settings.Validate();

Assert.True(result.Successful);
}

[Fact]
public void OutputFormatTypeConverterSupportsJsonPretty()
{
Expand Down
30 changes: 30 additions & 0 deletions AndroidSdk.Tool/AvdDeleteCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public class AvdDeleteCommandSettings : CommandSettings
[CommandOption("-h|--home")]
public string Home { get; set; }

[Description("Stop the running emulator (if any) before deleting and suppress all errors (cleanup mode)")]
[CommandOption("--force")]
[DefaultValue(false)]
public bool Force { get; set; }

public override ValidationResult Validate()
{
if (string.IsNullOrEmpty(Name))
Expand All @@ -35,15 +40,40 @@ public override int Execute([NotNull] CommandContext context, [NotNull] AvdDelet
{
try
{
if (settings.Force)
{
var emu = new Emulator(settings?.Home);
var stopped = emu.StopAvd(settings.Name, TimeSpan.FromSeconds(10));
if (stopped)
{
AnsiConsole.MarkupLine($"[yellow]Stopped running emulator for AVD '{settings.Name}'.[/]");
}
}

var avd = new AvdManager(settings?.Home);

avd.Delete(settings.Name);
}
catch (SdkToolFailedExitException sdkEx)
{
if (settings.Force)
{
// --force never fails (cleanup command)
AnsiConsole.MarkupLine($"[yellow]AVD '{settings.Name}' may not exist (ignored with --force)[/]");
return 0;
}
Program.WriteException(sdkEx);
return 1;
}
catch (Exception)
{
if (settings.Force)
{
AnsiConsole.MarkupLine($"[yellow]Warning: failed to force-delete AVD '{settings.Name}' due to unexpected error.[/]");
return 0;
}
throw;
}
return 0;
}
}
Expand Down
49 changes: 46 additions & 3 deletions AndroidSdk.Tool/AvdStartCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -134,6 +133,16 @@ public class AvdStartCommandSettings : CommandSettings
[DefaultValue(null)]
public bool? GrpcUseJwt { get; set; }

[Description("Disable all window, transition, and animator animation scales on the emulator (persists for this AVD until changed)")]
[CommandOption("--disable-animations")]
[DefaultValue(false)]
public bool DisableAnimations { get; set; }

[Description("Wait for guest CPU load average to drop below this threshold before proceeding")]
[CommandOption("--cpu-threshold")]
[DefaultValue(null)]
public double? CpuThreshold { get; set; }

public override ValidationResult Validate()
{
if (string.IsNullOrEmpty(Name))
Expand Down Expand Up @@ -211,12 +220,37 @@ public override Task<int> ExecuteAsync([NotNull] CommandContext context, [NotNul
process?.Shutdown();
});

var timeout = settings.Timeout.HasValue ? TimeSpan.FromSeconds(settings.Timeout.Value) : TimeSpan.Zero;
var timeoutBudget = settings.Timeout.HasValue ? TimeSpan.FromSeconds(settings.Timeout.Value) : TimeSpan.Zero;
var waitStopwatch = System.Diagnostics.Stopwatch.StartNew();

if (settings.WaitForBoot)
{
ctx.Status($"Waiting for {settings.Name} to finish booting...");
ok = process.WaitForBootComplete(timeout);
ok = process.WaitForBootComplete(timeoutBudget);
}

if (ok && process?.Serial != null)
{
if (settings.DisableAnimations)
{
ctx.Status($"Disabling animations on {settings.Name}...");
process.DisableAnimations();
}

if (settings.CpuThreshold.HasValue)
{
ctx.Status($"Waiting for CPU load to drop below {settings.CpuThreshold.Value} on {settings.Name}...");
var cpuWaitTimeout = GetStepTimeout(timeoutBudget, waitStopwatch.Elapsed, TimeSpan.FromSeconds(120));
var cpuSettled = process.WaitForCpuLoadBelow(settings.CpuThreshold.Value, cpuWaitTimeout, TimeSpan.FromSeconds(10), cancellationToken);
if (cpuSettled)
{
ctx.Status("CPU settled and system stabilized");
}
else if (!cancellationToken.IsCancellationRequested)
{
AnsiConsole.MarkupLine("[yellow]Warning: CPU load did not settle within timeout[/]");
}
}
}

if (settings.WaitForExit)
Expand All @@ -241,5 +275,14 @@ public override Task<int> ExecuteAsync([NotNull] CommandContext context, [NotNul

return Task.FromResult(ok ? 0 : 1);
}

static TimeSpan GetStepTimeout(TimeSpan timeoutBudget, TimeSpan elapsed, TimeSpan fallback)
{
if (timeoutBudget == TimeSpan.Zero)
return fallback;

var remaining = timeoutBudget - elapsed;
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
}
}
}
Loading