string SubstituteEnvVariables(string path)
        {
            MatchEvaluator replaceEnvVar = (Match m) => {
                var e = m.Groups [2].Value;
                switch (e)
                {
                case "ANDROID_SDK_PATH":
                    return(AndroidSdkDirectory.TrimEnd(Path.DirectorySeparatorChar));

                case "ANDROID_NDK_PATH":
                    //NOTE: AndroidNdkDirectory is not [Required]
                    if (string.IsNullOrEmpty(AndroidNdkDirectory))
                    {
                        goto default;
                    }
                    return(AndroidNdkDirectory.TrimEnd(Path.DirectorySeparatorChar));

                default:
                    var v = Environment.GetEnvironmentVariable(e);
                    if (!string.IsNullOrEmpty(v))
                    {
                        return(v);
                    }
                    return(m.Groups [1].Value);
                }
            };

            return(regex.Replace(path, replaceEnvVar));
        }
        public override bool Execute()
        {
            Log.LogMessage(MessageImportance.High, $"Task {nameof (RunAndroidEmulatorCheckBootTimes)}");
            Log.LogMessage(MessageImportance.High, $"     {nameof (AndroidSdkDirectory)}: {AndroidSdkDirectory}");
            Log.LogMessage(MessageImportance.High, $"     {nameof (AvdManagerHome)}: {AvdManagerHome}");
            Log.LogMessage(MessageImportance.High, $"     {nameof (CheckBootTimesPath)}: {CheckBootTimesPath}");
            Log.LogMessage(MessageImportance.High, $"     {nameof (DeviceName)}: {DeviceName}");

            var fileInfo = new FileInfo(CheckBootTimesPath);

            Log.LogMessage(MessageImportance.High, $"CheckBootTimesPath:  {fileInfo.FullName}");

            if (!fileInfo.Exists)
            {
                Log.LogError($"Unable to find {fileInfo.FullName}.");
                return(!Log.HasLoggedErrors);
            }

            var  timeoutInMS      = (int)TimeSpan.FromMinutes(15).TotalMilliseconds;
            bool finishAsExpected = false;
            bool processTimedOut  = false;
            var  args             = new StringBuilder($"--devicename {DeviceName} --verbose");

            if (!string.IsNullOrWhiteSpace(AndroidSdkDirectory))
            {
                args.Append($" --sdkpath \"{AndroidSdkDirectory.TrimEnd ('\\')}\"");
            }
            if (!string.IsNullOrWhiteSpace(AvdManagerHome))
            {
                args.Append($" --avdhome \"{AvdManagerHome.TrimEnd ('\\')}\"");
            }
            var success = RunProcess(
                fileInfo.FullName,
                args.ToString(),
                timeoutInMS,
                (string data, ManualResetEvent mre) => {
                Log.LogMessage(MessageImportance.High, $"CheckBootTimes ({DateTime.UtcNow}): {data}");
                if (!string.IsNullOrWhiteSpace(data) && data.IndexOf("Check-Boot-Times Done.", StringComparison.OrdinalIgnoreCase) != -1)
                {
                    finishAsExpected = true;
                    mre.Set();
                }

                return(true);
            },
                () => {
                processTimedOut = true;
            }
                );

            if (!success || !finishAsExpected)
            {
                if (processTimedOut)
                {
                    Log.LogError($"Process '{fileInfo.FullName} --devicename {DeviceName}' timed out after {timeoutInMS} ms.");
                }
                else
                {
                    Log.LogError("check-boot-times did not run as expected, please see logs for more info.");
                }
                CloseProcesses();
                return(false);
            }

            Log.LogMessage(MessageImportance.High, $"Done with RunAndroidEmulatorCheckBootTimes task.");
            return(true);
        }