You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb clayton.langford #ROBOMERGE-SOURCE: CL 6739909 via CL 6739922 via CL 6757015 #ROBOMERGE-BOT: (v365-6733468) [CL 6757271 by ben salem in Main branch]
1365 lines
40 KiB
C#
1365 lines
40 KiB
C#
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using AutomationTool;
|
|
using UnrealBuildTool;
|
|
using System.Text.RegularExpressions;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace Gauntlet
|
|
{
|
|
// device data from json
|
|
public sealed class AndroidDeviceData
|
|
{
|
|
// remote device settings (wifi)
|
|
|
|
// host of PC which is tethered
|
|
public string hostIP { get; set; }
|
|
|
|
// public key
|
|
public string publicKey { get; set; }
|
|
|
|
// private key
|
|
public string privateKey { get; set; }
|
|
}
|
|
|
|
// become IAppInstance when implemented enough
|
|
class AndroidAppInstance : IAppInstance
|
|
{
|
|
protected TargetDeviceAndroid AndroidDevice;
|
|
|
|
protected AndroidAppInstall Install;
|
|
|
|
internal IProcessResult LaunchProcess;
|
|
|
|
internal bool bHaveSavedArtifacts;
|
|
|
|
public string CommandLine { get { return Install.CommandLine; } }
|
|
|
|
public AndroidAppInstance(TargetDeviceAndroid InDevice, AndroidAppInstall InInstall, IProcessResult InProcess)
|
|
{
|
|
AndroidDevice = InDevice;
|
|
Install = InInstall;
|
|
LaunchProcess = InProcess;
|
|
}
|
|
|
|
public string ArtifactPath
|
|
{
|
|
get
|
|
{
|
|
if (bHaveSavedArtifacts == false)
|
|
{
|
|
if (HasExited)
|
|
{
|
|
SaveArtifacts();
|
|
bHaveSavedArtifacts = true;
|
|
}
|
|
}
|
|
|
|
return Path.Combine(AndroidDevice.LocalCachePath, "Saved");
|
|
}
|
|
}
|
|
|
|
public ITargetDevice Device
|
|
{
|
|
get
|
|
{
|
|
return AndroidDevice;
|
|
}
|
|
}
|
|
|
|
public bool HasExited
|
|
{
|
|
get
|
|
{
|
|
try
|
|
{
|
|
if (!LaunchProcess.HasExited)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
catch (System.InvalidOperationException)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return IsActivityRunning();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks on device whether the activity is running, this is an expensive shell with output operation
|
|
/// the result is cached, with checks at ActivityCheckDelta seconds
|
|
/// </summary>
|
|
private bool IsActivityRunning()
|
|
{
|
|
if (ActivityExited)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ((DateTime.UtcNow - ActivityCheckTime) < ActivityCheckDelta)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ActivityCheckTime = DateTime.UtcNow;
|
|
|
|
// get activities filtered by our package name
|
|
IProcessResult ActivityQuery = AndroidDevice.RunAdbDeviceCommand("shell dumpsys activity -p " + Install.AndroidPackageName + " a");
|
|
|
|
// We have exited if our activity doesn't appear in the activity query or is not the focused activity.
|
|
bool bActivityPresent = ActivityQuery.Output.Contains(Install.AndroidPackageName);
|
|
bool bActivityInForeground = ActivityQuery.Output.Contains("mResumedActivity");
|
|
bool bHasExited = !bActivityPresent || !bActivityInForeground;
|
|
if (bHasExited)
|
|
{
|
|
ActivityExited = true;
|
|
// Make sure entire activity log has been captured
|
|
UpdateCachedLog(true);
|
|
Log.VeryVerbose("{0}: process exited, Activity running={1}, Activity in foreground={2} ", ToString(), bActivityPresent.ToString(), bActivityInForeground.ToString());
|
|
}
|
|
|
|
return bHasExited;
|
|
|
|
}
|
|
|
|
private static readonly TimeSpan ActivityCheckDelta = TimeSpan.FromSeconds(10);
|
|
private DateTime ActivityCheckTime = DateTime.UtcNow;
|
|
private bool ActivityExited = false;
|
|
|
|
public bool WasKilled { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// The output of the test activity
|
|
/// </summary>
|
|
public string StdOut
|
|
{
|
|
get
|
|
{
|
|
UpdateCachedLog();
|
|
return String.IsNullOrEmpty(ActivityLogCached) ? String.Empty : ActivityLogCached;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Updates cached activity log by running a shell command returning the full log from device (possibly over wifi)
|
|
/// The result is cached and updated at ActivityLogDelta frequency
|
|
/// </summary>
|
|
private void UpdateCachedLog(bool ForceUpdate = false)
|
|
{
|
|
if (!ForceUpdate && (ActivityLogTime == DateTime.MinValue || ((DateTime.UtcNow - ActivityLogTime) < ActivityLogDelta)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Install.AndroidDevice != null && Install.AndroidDevice.Disposed)
|
|
{
|
|
Log.Warning("Attempting to cache log using disposed Android device");
|
|
return;
|
|
}
|
|
|
|
string GetLogCommand = string.Format("shell cat {0}/Logs/{1}.log", Install.AndroidDevice.DeviceArtifactPath, Install.Name);
|
|
IProcessResult LogQuery = Install.AndroidDevice.RunAdbDeviceCommand(GetLogCommand, true);
|
|
|
|
if (LogQuery.ExitCode != 0)
|
|
{
|
|
Log.VeryVerbose("Unable to query activity stdout on device {0}", Install.AndroidDevice.Name);
|
|
}
|
|
else
|
|
{
|
|
ActivityLogCached = LogQuery.Output;
|
|
}
|
|
|
|
ActivityLogTime = DateTime.UtcNow;
|
|
|
|
// the activity has exited, mark final log sentinel
|
|
if (ActivityExited)
|
|
{
|
|
ActivityLogTime = DateTime.MinValue;
|
|
}
|
|
|
|
}
|
|
|
|
private static readonly TimeSpan ActivityLogDelta = TimeSpan.FromSeconds(15);
|
|
private DateTime ActivityLogTime = DateTime.UtcNow - ActivityLogDelta;
|
|
private string ActivityLogCached = string.Empty;
|
|
|
|
|
|
public int WaitForExit()
|
|
{
|
|
if (!HasExited)
|
|
{
|
|
LaunchProcess.WaitForExit();
|
|
}
|
|
|
|
return ExitCode;
|
|
}
|
|
|
|
public void Kill()
|
|
{
|
|
if (!HasExited)
|
|
{
|
|
WasKilled = true;
|
|
Install.AndroidDevice.KillRunningProcess(Install.AndroidPackageName);
|
|
}
|
|
}
|
|
public int ExitCode { get { return LaunchProcess.ExitCode; } }
|
|
|
|
protected void SaveArtifacts()
|
|
{
|
|
// copy remote artifacts to local
|
|
if (Directory.Exists(Install.AndroidDevice.LocalCachePath))
|
|
{
|
|
try
|
|
{
|
|
// don't consider this fatal, people often have the directory or a file open
|
|
Directory.Delete(Install.AndroidDevice.LocalCachePath, true);
|
|
}
|
|
catch
|
|
{
|
|
Log.Warning("Failed to remove old cache folder {0}", Install.AndroidDevice.LocalCachePath);
|
|
}
|
|
}
|
|
|
|
// mark it as a temp dir (will also create it)
|
|
Utils.SystemHelpers.MarkDirectoryForCleanup(Install.AndroidDevice.LocalCachePath);
|
|
|
|
string LocalSaved = Path.Combine(Install.AndroidDevice.LocalCachePath, "Saved");
|
|
Directory.CreateDirectory(LocalSaved);
|
|
|
|
// pull all the artifacts
|
|
string ArtifactPullCommand = string.Format("pull {0} {1}", Install.AndroidDevice.DeviceArtifactPath, Install.AndroidDevice.LocalCachePath);
|
|
IProcessResult PullCmd = Install.AndroidDevice.RunAdbDeviceCommand(ArtifactPullCommand);
|
|
|
|
if (PullCmd.ExitCode != 0)
|
|
{
|
|
Log.Warning("Failed to retrieve artifacts. {0}", PullCmd.Output);
|
|
}
|
|
else
|
|
{
|
|
// update final cached stdout property
|
|
string LogFilename = string.Format("{0}/Logs/{1}.log", LocalSaved, Install.Name);
|
|
if (File.Exists(LogFilename))
|
|
{
|
|
ActivityLogCached = File.ReadAllText(LogFilename);
|
|
ActivityLogTime = DateTime.MinValue;
|
|
}
|
|
|
|
}
|
|
|
|
// pull the logcat over from device.
|
|
IProcessResult LogcatResult = Install.AndroidDevice.RunAdbDeviceCommand("logcat -d");
|
|
|
|
string LogcatFilename = "Logcat.log";
|
|
// Save logcat dump to local artifact path.
|
|
File.WriteAllText(Path.Combine(LocalSaved, LogcatFilename), LogcatResult.Output);
|
|
|
|
Install.AndroidDevice.PostRunCleanup();
|
|
}
|
|
}
|
|
|
|
class AndroidAppInstall : IAppInstall
|
|
{
|
|
public string Name { get; protected set; }
|
|
|
|
public string AndroidPackageName { get; protected set; }
|
|
|
|
public TargetDeviceAndroid AndroidDevice { get; protected set; }
|
|
|
|
public ITargetDevice Device { get { return AndroidDevice; } }
|
|
|
|
public string CommandLine { get; protected set; }
|
|
|
|
public IAppInstance Run()
|
|
{
|
|
return AndroidDevice.Run(this);
|
|
}
|
|
|
|
public AndroidAppInstall(TargetDeviceAndroid InDevice, string InName, string InAndroidPackageName, string InCommandLine)
|
|
{
|
|
AndroidDevice = InDevice;
|
|
Name = InName;
|
|
AndroidPackageName = InAndroidPackageName;
|
|
CommandLine = InCommandLine;
|
|
}
|
|
}
|
|
|
|
public class DefaultAndroidDevices : IDefaultDeviceSource
|
|
{
|
|
public bool CanSupportPlatform(UnrealTargetPlatform? Platform)
|
|
{
|
|
return Platform == UnrealTargetPlatform.Android;
|
|
}
|
|
|
|
public ITargetDevice[] GetDefaultDevices()
|
|
{
|
|
return TargetDeviceAndroid.GetDefaultDevices();
|
|
}
|
|
}
|
|
|
|
public class AndroidDeviceFactory : IDeviceFactory
|
|
{
|
|
public bool CanSupportPlatform(UnrealTargetPlatform? Platform)
|
|
{
|
|
return Platform == UnrealTargetPlatform.Android;
|
|
}
|
|
|
|
public ITargetDevice CreateDevice(string InRef, string InParam)
|
|
{
|
|
AndroidDeviceData DeviceData = null;
|
|
|
|
if (!String.IsNullOrEmpty(InParam))
|
|
{
|
|
DeviceData = fastJSON.JSON.Instance.ToObject<AndroidDeviceData>(InParam);
|
|
}
|
|
|
|
return new TargetDeviceAndroid(InRef, DeviceData);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Android implementation of a device that can run applications
|
|
/// </summary>
|
|
public class TargetDeviceAndroid : ITargetDevice
|
|
{
|
|
/// <summary>
|
|
/// Friendly name for this target
|
|
/// </summary>
|
|
public string Name { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// Low-level device name
|
|
/// </summary>
|
|
public string DeviceName { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// Platform type.
|
|
/// </summary>
|
|
public UnrealTargetPlatform? Platform { get { return UnrealTargetPlatform.Android; } }
|
|
|
|
/// <summary>
|
|
/// Options for executing commands
|
|
/// </summary>
|
|
public CommandUtils.ERunOptions RunOptions { get; set; }
|
|
|
|
/// <summary>
|
|
/// Temp path we use to push/pull things from the device
|
|
/// </summary>
|
|
public string LocalCachePath { get; protected set; }
|
|
|
|
|
|
/// <summary>
|
|
/// Artifact (e.g. Saved) path on the device
|
|
/// </summary>
|
|
public string DeviceArtifactPath { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// Path to a command line if installed
|
|
/// </summary>
|
|
protected string CommandLineFilePath { get; set; }
|
|
|
|
public bool IsAvailable
|
|
{
|
|
get
|
|
{
|
|
// ensure our device is present in 'adb devices' output.
|
|
var AllDevices = GetAllConnectedDevices();
|
|
|
|
if (AllDevices.Keys.Contains(DeviceName) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (AllDevices[DeviceName] == false)
|
|
{
|
|
Log.Warning("Device {0} is connected but we are not authorized", DeviceName);
|
|
return false;
|
|
}
|
|
|
|
// any device will do, but only one at a time.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
protected Dictionary<EIntendedBaseCopyDirectory, string> LocalDirectoryMappings { get; set; }
|
|
void SetUpDirectoryMappings()
|
|
{
|
|
LocalDirectoryMappings = new Dictionary<EIntendedBaseCopyDirectory, string>();
|
|
}
|
|
|
|
public void PopulateDirectoryMappings(string ProjectDir)
|
|
{
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Build, Path.Combine(ProjectDir, "Build"));
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Binaries, Path.Combine(ProjectDir, "Binaries"));
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Config, Path.Combine(ProjectDir, "Config"));
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Content, Path.Combine(ProjectDir, "Content"));
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Demos, Path.Combine(ProjectDir, "Demos"));
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Profiling, Path.Combine(ProjectDir, "Profiling"));
|
|
LocalDirectoryMappings.Add(EIntendedBaseCopyDirectory.Saved, ProjectDir);
|
|
}
|
|
public bool IsConnected { get { return IsAvailable; } }
|
|
|
|
protected bool IsExistingDevice = false;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="InReferenceName"></param>
|
|
/// <param name="InRemoveOnDestruction"></param>
|
|
public TargetDeviceAndroid(string InDeviceName = "", AndroidDeviceData DeviceData = null)
|
|
{
|
|
DeviceName = InDeviceName;
|
|
|
|
AdbCredentialCache.AddInstance(DeviceData);
|
|
|
|
// If no device name or its 'default' then use the first default device
|
|
if (string.IsNullOrEmpty(DeviceName) || DeviceName.Equals("default", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var DefaultDevices = GetAllAvailableDevices();
|
|
|
|
if (DefaultDevices.Count() == 0)
|
|
{
|
|
if (GetAllConnectedDevices().Count > 0)
|
|
{
|
|
throw new AutomationException("No default device available. One or more devices are connected but unauthorized. See 'adb devices'");
|
|
}
|
|
else
|
|
{
|
|
throw new AutomationException("No default device available. See 'adb devices'");
|
|
}
|
|
}
|
|
|
|
DeviceName = DefaultDevices.First();
|
|
}
|
|
|
|
if (Log.IsVerbose)
|
|
{
|
|
RunOptions = CommandUtils.ERunOptions.NoWaitForExit;
|
|
}
|
|
else
|
|
{
|
|
RunOptions = CommandUtils.ERunOptions.NoWaitForExit | CommandUtils.ERunOptions.NoLoggingOfRunCommand;
|
|
}
|
|
|
|
// if this is not a connected device then remove when done
|
|
var ConnectedDevices = GetAllConnectedDevices();
|
|
|
|
IsExistingDevice = ConnectedDevices.Keys.Contains(DeviceName);
|
|
|
|
if (!IsExistingDevice)
|
|
{
|
|
// adb uses 5555 by default
|
|
if (DeviceName.Contains(":") == false)
|
|
{
|
|
DeviceName = DeviceName + ":5555";
|
|
}
|
|
|
|
lock (Globals.MainLock)
|
|
{
|
|
using (var PauseEC = new ScopedSuspendECErrorParsing())
|
|
{
|
|
IProcessResult AdbResult = RunAdbGlobalCommand(string.Format("connect {0}", DeviceName));
|
|
|
|
if (AdbResult.ExitCode != 0)
|
|
{
|
|
throw new AutomationException("adb failed to connect to {0}. {1}", DeviceName, AdbResult.Output);
|
|
}
|
|
}
|
|
|
|
Log.Info("Connected to {0}", DeviceName);
|
|
|
|
// Need to sleep for adb service process to register, otherwise get an unauthorized (especially on parallel device use)
|
|
Thread.Sleep(5000);
|
|
}
|
|
}
|
|
|
|
LocalDirectoryMappings = new Dictionary<EIntendedBaseCopyDirectory, string>();
|
|
|
|
// for IP devices need to sanitize this
|
|
Name = DeviceName.Replace(":", "_");
|
|
|
|
// Path we use for artifacts, we'll create it later when we need it
|
|
LocalCachePath = Path.Combine(Globals.TempDir, "AndroidDevice_" + Name);
|
|
|
|
ConnectedDevices = GetAllConnectedDevices();
|
|
|
|
SetUpDirectoryMappings();
|
|
|
|
// sanity check that it was now dound
|
|
if (ConnectedDevices.Keys.Contains(DeviceName) == false)
|
|
{
|
|
throw new AutomationException("Failed to find new device {0} in connection list", DeviceName);
|
|
}
|
|
|
|
if (ConnectedDevices[DeviceName] == false)
|
|
{
|
|
Dispose();
|
|
throw new AutomationException("Device {0} is connected but this PC is not authorized.", DeviceName);
|
|
}
|
|
}
|
|
|
|
~TargetDeviceAndroid()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#region IDisposable Support
|
|
private bool disposedValue = false; // To detect redundant calls
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (!disposedValue)
|
|
{
|
|
try
|
|
{
|
|
if (!IsExistingDevice)
|
|
{
|
|
// disconnect
|
|
RunAdbGlobalCommand(string.Format("disconnect {0}", DeviceName), true, false, true);
|
|
|
|
Log.Info("Disconnected {0}", DeviceName);
|
|
}
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
Log.Warning("TargetDeviceAndroid.Dispose() threw: {0}", Ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
disposedValue = true;
|
|
AdbCredentialCache.RemoveInstance();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// This code added to correctly implement the disposable pattern.
|
|
public void Dispose()
|
|
{
|
|
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
|
|
Dispose(true);
|
|
// TODO: uncomment the following line if the finalizer is overridden above.
|
|
// GC.SuppressFinalize(this);
|
|
}
|
|
|
|
public bool Disposed
|
|
{
|
|
get
|
|
{
|
|
return disposedValue;
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns a list of locally connected devices (e.g. 'adb devices').
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
static private Dictionary<string, bool> GetAllConnectedDevices()
|
|
{
|
|
var Result = RunAdbGlobalCommand("devices");
|
|
|
|
MatchCollection DeviceMatches = Regex.Matches(Result.Output, @"^([\d\w\.\:]{6,32})\s+(\w+)", RegexOptions.Multiline);
|
|
|
|
var DeviceList = DeviceMatches.Cast<Match>().ToDictionary(
|
|
M => M.Groups[1].ToString(),
|
|
M => !M.Groups[2].ToString().ToLower().Contains("unauthorized")
|
|
);
|
|
|
|
return DeviceList;
|
|
}
|
|
|
|
static private IEnumerable<string> GetAllAvailableDevices()
|
|
{
|
|
var AllDevices = GetAllConnectedDevices();
|
|
return AllDevices.Keys.Where(D => AllDevices[D] == true);
|
|
}
|
|
|
|
static public ITargetDevice[] GetDefaultDevices()
|
|
{
|
|
var Result = RunAdbGlobalCommand("devices");
|
|
|
|
MatchCollection DeviceMatches = Regex.Matches(Result.Output, @"([\d\w\.\:]{8,32})\s+device");
|
|
|
|
List<ITargetDevice> Devices = new List<ITargetDevice>();
|
|
|
|
foreach (string Device in GetAllAvailableDevices())
|
|
{
|
|
ITargetDevice NewDevice = new TargetDeviceAndroid(Device);
|
|
Devices.Add(NewDevice);
|
|
}
|
|
|
|
return Devices.ToArray();
|
|
}
|
|
|
|
internal void PostRunCleanup()
|
|
{
|
|
// Delete the commandline file, if someone installs an APK on top of ours
|
|
// they will get very confusing behavior...
|
|
if (string.IsNullOrEmpty(CommandLineFilePath) == false)
|
|
{
|
|
Log.Verbose("Removing {0}", CommandLineFilePath);
|
|
DeleteFileFromDevice(CommandLineFilePath);
|
|
CommandLineFilePath = null;
|
|
}
|
|
}
|
|
|
|
public bool IsOn
|
|
{
|
|
get
|
|
{
|
|
string CommandLine = "shell dumpsys power";
|
|
IProcessResult OnAndUnlockedQuery = RunAdbDeviceCommand(CommandLine);
|
|
|
|
return OnAndUnlockedQuery.Output.Contains("mHoldingDisplaySuspendBlocker=true")
|
|
&& OnAndUnlockedQuery.Output.Contains("mHoldingWakeLockSuspendBlocker=true");
|
|
}
|
|
}
|
|
|
|
public bool PowerOn()
|
|
{
|
|
Log.Verbose("{0}: Powering on", ToString());
|
|
string CommandLine = "shell \"input keyevent KEYCODE_WAKEUP && input keyevent KEYCODE_MENU\"";
|
|
RunAdbDeviceCommand(CommandLine);
|
|
return true;
|
|
}
|
|
public bool PowerOff()
|
|
{
|
|
Log.Verbose("{0}: Powering off", ToString());
|
|
|
|
string CommandLine = "shell \"input keyevent KEYCODE_SLEEP\"";
|
|
RunAdbDeviceCommand(CommandLine);
|
|
return true;
|
|
}
|
|
|
|
public bool Reboot()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public bool Connect()
|
|
{
|
|
AllowDeviceSleepState(true);
|
|
return true;
|
|
}
|
|
|
|
public bool Disconnect()
|
|
{
|
|
AllowDeviceSleepState(false);
|
|
return true;
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
// TODO: device id
|
|
if (Name == DeviceName)
|
|
{
|
|
return Name;
|
|
}
|
|
return string.Format("{0} ({1})", Name, DeviceName);
|
|
}
|
|
|
|
protected bool DeleteFileFromDevice(string DestPath)
|
|
{
|
|
var AdbResult = RunAdbDeviceCommand(string.Format("shell rm -f {0}", DestPath));
|
|
return AdbResult.ExitCode == 0;
|
|
}
|
|
|
|
public bool CopyFileToDevice(string PackageName, string SourcePath, string DestPath, bool IgnoreDependencies = false)
|
|
{
|
|
bool IsAPK = string.Equals(Path.GetExtension(SourcePath), ".apk", StringComparison.OrdinalIgnoreCase);
|
|
|
|
// for the APK there's no easy/reliable way to get the date of the version installed, so
|
|
// we write this out to a dependency file in the demote dir and check it each time.
|
|
// current file time
|
|
DateTime LocalModifiedTime = File.GetLastWriteTime(SourcePath);
|
|
|
|
string QuotedSourcePath = SourcePath;
|
|
if (SourcePath.Contains(" "))
|
|
{
|
|
QuotedSourcePath = '"' + SourcePath + '"';
|
|
}
|
|
|
|
// dependency info is a hash of the destination name, saved under a folder on /sdcard
|
|
int DestHash = DestPath.GetHashCode();
|
|
string DependencyCacheDir = "/sdcard/gdeps";
|
|
string DepFile = string.Format("{0}/{1:X}", DependencyCacheDir, DestHash);
|
|
|
|
IProcessResult AdbResult = null;
|
|
|
|
|
|
// get info from the device about this file
|
|
string CurrentFileInfo = null;
|
|
|
|
if (IsAPK)
|
|
{
|
|
// for APK query the package info and get the update time
|
|
AdbResult = RunAdbDeviceCommand(string.Format("shell dumpsys package {0} | grep lastUpdateTime", PackageName));
|
|
|
|
if (AdbResult.ExitCode == 0)
|
|
{
|
|
CurrentFileInfo = AdbResult.Output.ToString().Trim();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// for other files get the file info
|
|
AdbResult = RunAdbDeviceCommand(string.Format("shell ls -l {0}", DestPath));
|
|
|
|
if (AdbResult.ExitCode == 0)
|
|
{
|
|
CurrentFileInfo = AdbResult.Output.ToString().Trim();
|
|
}
|
|
}
|
|
|
|
bool SkipInstall = false;
|
|
|
|
// If this is valid then there is some form of that file on the device, now figure out if it matches the
|
|
if (string.IsNullOrEmpty(CurrentFileInfo) == false)
|
|
{
|
|
// read the dep file
|
|
AdbResult = RunAdbDeviceCommand(string.Format("shell cat {0}", DepFile));
|
|
|
|
if (AdbResult.ExitCode == 0)
|
|
{
|
|
// Dependency info is the modified time of the source, and the post-copy file stats of the installed file, separated by ###
|
|
string[] DepLines = AdbResult.Output.ToString().Split(new[] { "###" }, StringSplitOptions.RemoveEmptyEntries).Select(S => S.Trim()).ToArray();
|
|
|
|
if (DepLines.Length >= 2)
|
|
{
|
|
string InstalledSourceModifiedTime = DepLines[0];
|
|
string InstalledFileInfo = DepLines[1];
|
|
|
|
if (InstalledSourceModifiedTime == LocalModifiedTime.ToString()
|
|
&& CurrentFileInfo == InstalledFileInfo)
|
|
{
|
|
SkipInstall = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SkipInstall && IgnoreDependencies == false)
|
|
{
|
|
Log.Info("Skipping install of {0} - remote file up to date", Path.GetFileName(SourcePath));
|
|
}
|
|
else
|
|
{
|
|
if (IsAPK)
|
|
{
|
|
// we need to ununstall then install the apk - don't care if it fails, may have been deleted
|
|
string AdbCommand = string.Format("uninstall {0}", PackageName);
|
|
AdbResult = RunAdbDeviceCommand(AdbCommand);
|
|
|
|
Log.Info("Installing {0} to {1}", SourcePath, Name);
|
|
|
|
AdbCommand = string.Format("install {0}", QuotedSourcePath);
|
|
AdbResult = RunAdbDeviceCommand(AdbCommand);
|
|
|
|
if (AdbResult.ExitCode != 0)
|
|
{
|
|
throw new AutomationException("Failed to install {0}. Error {1}", SourcePath, AdbResult.Output);
|
|
}
|
|
|
|
// for APK query the package info and get the update time
|
|
AdbResult = RunAdbDeviceCommand(string.Format("shell dumpsys package {0} | grep lastUpdateTime", PackageName));
|
|
CurrentFileInfo = AdbResult.Output.ToString().Trim();
|
|
}
|
|
else
|
|
{
|
|
Log.Info("Copying {0} to {1} via adb push", QuotedSourcePath, DestPath);
|
|
string AdbCommand = string.Format("push {0} {1}", QuotedSourcePath, DestPath);
|
|
AdbResult = RunAdbDeviceCommand(AdbCommand);
|
|
|
|
if (AdbResult.ExitCode != 0)
|
|
{
|
|
throw new AutomationException("Failed to push {0} to device. Error {1}", SourcePath, AdbResult.Output);
|
|
}
|
|
|
|
// Now pull info about the file which we'll write as a dep
|
|
AdbResult = RunAdbDeviceCommand(string.Format("shell ls -l {0}", DestPath));
|
|
CurrentFileInfo = AdbResult.Output.ToString().Trim();
|
|
}
|
|
|
|
// write the actual dependency info
|
|
string DepContents = LocalModifiedTime + "###" + CurrentFileInfo;
|
|
|
|
// save last modified time to remote deps after success
|
|
RunAdbDeviceCommand(string.Format("shell mkdir -p {0}", DependencyCacheDir));
|
|
|
|
string Cmd = string.Format("shell echo \"{0}\" > {1}", DepContents, DepFile);
|
|
AdbResult = RunAdbDeviceCommand(Cmd);
|
|
|
|
if (AdbResult.ExitCode != 0)
|
|
{
|
|
Log.Warning("Failed to write dependency file {0}", DepFile);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public IAppInstall InstallApplication(UnrealAppConfig AppConfig)
|
|
{
|
|
// todo - pass this through
|
|
AndroidBuild Build = AppConfig.Build as AndroidBuild;
|
|
|
|
// Ensure APK exists
|
|
if (Build == null)
|
|
{
|
|
throw new AutomationException("Invalid build for Android!");
|
|
}
|
|
|
|
// kill any currently running instance:
|
|
KillRunningProcess(Build.AndroidPackageName);
|
|
|
|
bool SkipDeploy = Globals.Params.ParseParam("SkipDeploy");
|
|
|
|
if (SkipDeploy == false)
|
|
{
|
|
// Establish remote directory locations
|
|
string DeviceStorageQueryCommand = AndroidPlatform.GetStorageQueryCommand();
|
|
IProcessResult StorageQueryResult = RunAdbDeviceCommand(DeviceStorageQueryCommand);
|
|
string StorageLocation = StorageQueryResult.Output.Trim(); // "/mnt/sdcard";
|
|
|
|
// remote dir used to save things
|
|
string RemoteDir = StorageLocation + "/UE4Game/" + AppConfig.ProjectName;
|
|
|
|
// if not a bulk/dev build, remote dir will be under /{StorageLocation}/Android/data/{PackageName}
|
|
if ((Build.Flags & ( BuildFlags.Bulk | BuildFlags.CanReplaceExecutable)) == 0)
|
|
{
|
|
RemoteDir = StorageLocation + "/Android/data/" + Build.AndroidPackageName + "/files/UE4Game/" + AppConfig.ProjectName;
|
|
}
|
|
|
|
string DependencyDir = RemoteDir + "/deps";
|
|
|
|
// device artifact path, always clear between runs
|
|
DeviceArtifactPath = string.Format("{0}/{1}/Saved", RemoteDir, AppConfig.ProjectName);
|
|
RunAdbDeviceCommand(string.Format("shell rm -r {0}", DeviceArtifactPath));
|
|
|
|
// path for OBB files
|
|
string OBBRemoteDestination = string.Format("{0}/obb/{1}", StorageLocation, Build.AndroidPackageName);
|
|
|
|
if (Globals.Params.ParseParam("cleandevice"))
|
|
{
|
|
Log.Info("Cleaning previous builds due to presence of -cleandevice");
|
|
|
|
// we need to ununstall then install the apk - don't care if it fails, may have been deleted
|
|
Log.Info("Uninstalling {0}", Build.AndroidPackageName);
|
|
RunAdbDeviceCommand(string.Format("uninstall {0}", Build.AndroidPackageName));
|
|
|
|
Log.Info("Removing {0}", RemoteDir);
|
|
RunAdbDeviceCommand(string.Format("shell rm -r {0}", RemoteDir));
|
|
|
|
Log.Info("Removing {0}", OBBRemoteDestination);
|
|
RunAdbDeviceCommand(string.Format("shell rm -r {0}", OBBRemoteDestination));
|
|
}
|
|
|
|
// remote dir on the device, create it if it doesn't exist
|
|
RunAdbDeviceCommand(string.Format("shell mkdir -p {0}/", RemoteDir));
|
|
|
|
IProcessResult AdbResult;
|
|
string AdbCommand;
|
|
|
|
// path to the APK to install.
|
|
string ApkPath = Build.SourceApkPath;
|
|
|
|
// check for a local newer executable
|
|
if (Globals.Params.ParseParam("dev"))
|
|
{
|
|
//string ApkFileName = Path.GetFileName(ApkPath);
|
|
|
|
string ApkFileName2 = UnrealHelpers.GetExecutableName(AppConfig.ProjectName, UnrealTargetPlatform.Android, AppConfig.Configuration, AppConfig.ProcessType, "apk");
|
|
|
|
string LocalAPK = Path.Combine(Environment.CurrentDirectory, AppConfig.ProjectName, "Binaries/Android", ApkFileName2);
|
|
|
|
bool LocalFileExists = File.Exists(LocalAPK);
|
|
bool LocalFileNewer = LocalFileExists && File.GetLastWriteTime(LocalAPK) > File.GetLastWriteTime(ApkPath);
|
|
|
|
Log.Verbose("Checking for newer binary at {0}", LocalAPK);
|
|
Log.Verbose("LocalFile exists: {0}. Newer: {1}", LocalFileExists, LocalFileNewer);
|
|
|
|
if (LocalFileExists && LocalFileNewer)
|
|
{
|
|
ApkPath = LocalAPK;
|
|
}
|
|
}
|
|
|
|
// first install the APK
|
|
CopyFileToDevice(Build.AndroidPackageName, ApkPath, "");
|
|
|
|
// obb files need to be named based on APK version (grrr), so find that out. This should return something like
|
|
// versionCode=2 minSdk=21 targetSdk=21
|
|
string PackageInfo = RunAdbDeviceCommand(string.Format("shell dumpsys package {0} | grep versionCode", Build.AndroidPackageName)).Output;
|
|
var Match = Regex.Match(PackageInfo, @"versionCode=([\d\.]+)\s");
|
|
if (Match.Success == false)
|
|
{
|
|
throw new AutomationException("Failed to find version info for APK!");
|
|
}
|
|
string PackageVersion = Match.Groups[1].ToString();
|
|
|
|
// Convert the files from the source to final destination names
|
|
Dictionary<string, string> FilesToInstall = new Dictionary<string, string>();
|
|
|
|
Console.WriteLine("trying to copy files over.");
|
|
if (AppConfig.FilesToCopy != null)
|
|
{
|
|
if (LocalDirectoryMappings.Count == 0)
|
|
{
|
|
Console.WriteLine("Populating Directory");
|
|
PopulateDirectoryMappings(DeviceArtifactPath);
|
|
}
|
|
Console.WriteLine("trying to copy files over.");
|
|
foreach (UnrealFileToCopy FileToCopy in AppConfig.FilesToCopy)
|
|
{
|
|
string PathToCopyTo = Path.Combine(LocalDirectoryMappings[FileToCopy.TargetBaseDirectory], FileToCopy.TargetRelativeLocation);
|
|
if (File.Exists(FileToCopy.SourceFileLocation))
|
|
{
|
|
FileInfo SrcInfo = new FileInfo(FileToCopy.SourceFileLocation);
|
|
SrcInfo.IsReadOnly = false;
|
|
FilesToInstall.Add(FileToCopy.SourceFileLocation, PathToCopyTo.Replace("\\", "/"));
|
|
Console.WriteLine("Copying {0} to {1}", FileToCopy.SourceFileLocation, PathToCopyTo);
|
|
}
|
|
|
|
else
|
|
{
|
|
Log.Warning("File to copy {0} not found", FileToCopy);
|
|
}
|
|
}
|
|
}
|
|
|
|
Build.FilesToInstall.Keys.ToList().ForEach(K =>
|
|
{
|
|
|
|
string SrcPath = K;
|
|
string DestPath = Build.FilesToInstall[K];
|
|
|
|
string DestFile = Path.GetFileName(DestPath);
|
|
|
|
// If we installed a new APK we need to change the package version
|
|
Match OBBMatch = Regex.Match(DestFile, @"\.(\d+)\.com.*\.obb");
|
|
if (OBBMatch.Success)
|
|
{
|
|
string NewFileName = DestFile.Replace(OBBMatch.Groups[1].ToString(), PackageVersion);
|
|
DestPath = DestPath.Replace(DestFile, NewFileName);
|
|
}
|
|
|
|
DestPath = Regex.Replace(DestPath, "%STORAGE%", StorageLocation, RegexOptions.IgnoreCase);
|
|
|
|
FilesToInstall.Add(SrcPath, DestPath);
|
|
});
|
|
|
|
|
|
|
|
// get a list of files in the destination OBB directory
|
|
AdbResult = RunAdbDeviceCommand(string.Format("shell ls {0}", OBBRemoteDestination));
|
|
|
|
// if != 0 then no folder exists
|
|
if (AdbResult.ExitCode == 0)
|
|
{
|
|
IEnumerable<string> CurrentRemoteFileList = AdbResult.Output.Replace("\r\n", "\n").Split('\n');
|
|
IEnumerable<string> NewRemoteFileList = FilesToInstall.Values.Select(F => Path.GetFileName(F));
|
|
|
|
// delete any files that should not be there
|
|
foreach (string FileName in CurrentRemoteFileList)
|
|
{
|
|
if (FileName.StartsWith(".") || FileName.Length == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (NewRemoteFileList.Contains(FileName) == false)
|
|
{
|
|
RunAdbDeviceCommand(string.Format("shell rm {0}/{1}", OBBRemoteDestination, FileName));
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var KV in FilesToInstall)
|
|
{
|
|
string LocalFile = KV.Key;
|
|
string RemoteFile = KV.Value;
|
|
|
|
CopyFileToDevice(Build.AndroidPackageName, LocalFile, RemoteFile);
|
|
}
|
|
|
|
// create a tempfile, insert the command line, and push it over
|
|
string TmpFile = Path.GetTempFileName();
|
|
|
|
CommandLineFilePath = string.Format("{0}/UE4CommandLine.txt", RemoteDir);
|
|
|
|
// I've seen a weird thing where adb push truncates by a byte, so add some padding...
|
|
File.WriteAllText(TmpFile, AppConfig.CommandLine + " ");
|
|
AdbCommand = string.Format("push {0} {1}", TmpFile, CommandLineFilePath);
|
|
RunAdbDeviceCommand(AdbCommand);
|
|
|
|
EnablePermissions(Build.AndroidPackageName);
|
|
|
|
File.Delete(TmpFile);
|
|
}
|
|
else
|
|
{
|
|
Log.Info("Skipping install of {0} (-skipdeploy)", Build.AndroidPackageName);
|
|
}
|
|
|
|
AndroidAppInstall AppInstall = new AndroidAppInstall(this, AppConfig.ProjectName, Build.AndroidPackageName, AppConfig.CommandLine);
|
|
|
|
return AppInstall;
|
|
}
|
|
|
|
public IAppInstance Run(IAppInstall App)
|
|
{
|
|
AndroidAppInstall DroidAppInstall = App as AndroidAppInstall;
|
|
|
|
if (DroidAppInstall == null)
|
|
{
|
|
throw new Exception("AppInstance is of incorrect type!");
|
|
}
|
|
|
|
// wake the device - we can install while its asleep but not run
|
|
PowerOn();
|
|
|
|
// kill any currently running instance:
|
|
KillRunningProcess(DroidAppInstall.AndroidPackageName);
|
|
|
|
string LaunchActivity = AndroidPlatform.GetLaunchableActivityName();
|
|
|
|
Log.Info("Launching {0} on '{1}' ", DroidAppInstall.AndroidPackageName + "/" + LaunchActivity, ToString());
|
|
Log.Verbose("\t{0}", DroidAppInstall.CommandLine);
|
|
|
|
// Clear the device's logcat in preparation for the test..
|
|
RunAdbDeviceCommand("logcat --clear");
|
|
|
|
// start the app on device!
|
|
string CommandLine = "shell am start -W -S -n " + DroidAppInstall.AndroidPackageName + "/" + LaunchActivity;
|
|
IProcessResult Process = RunAdbDeviceCommand(CommandLine, false, true);
|
|
|
|
return new AndroidAppInstance(this, DroidAppInstall, Process);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs an ADB command, automatically adding the name of the current device to
|
|
/// the arguments sent to adb
|
|
/// </summary>
|
|
/// <param name="Args"></param>
|
|
/// <param name="Wait"></param>
|
|
/// <param name="Input"></param>
|
|
/// <returns></returns>
|
|
public IProcessResult RunAdbDeviceCommand(string Args, bool Wait=true, bool bShouldLogCommand = false, bool bPauseErrorParsing = false)
|
|
{
|
|
if (string.IsNullOrEmpty(DeviceName) == false)
|
|
{
|
|
Args = string.Format("-s {0} {1}", DeviceName, Args);
|
|
}
|
|
|
|
return RunAdbGlobalCommand(Args, Wait, bShouldLogCommand, bPauseErrorParsing);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs an ADB command, automatically adding the name of the current device to
|
|
/// the arguments sent to adb
|
|
/// </summary>
|
|
/// <param name="Args"></param>
|
|
/// <param name="Wait"></param>
|
|
/// <param name="Input"></param>
|
|
/// <returns></returns>
|
|
public string RunAdbDeviceCommandAndGetOutput(string Args)
|
|
{
|
|
if (string.IsNullOrEmpty(DeviceName) == false)
|
|
{
|
|
Args = string.Format("-s {0} {1}", DeviceName, Args);
|
|
}
|
|
|
|
IProcessResult Result = RunAdbGlobalCommand(Args);
|
|
|
|
if (Result.ExitCode != 0)
|
|
{
|
|
throw new AutomationException("adb command {0} failed. {1}", Args, Result.Output);
|
|
}
|
|
|
|
return Result.Output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Runs an ADB command at the global scope
|
|
/// </summary>
|
|
/// <param name="Args"></param>
|
|
/// <param name="Wait"></param>
|
|
/// <returns></returns>
|
|
public static IProcessResult RunAdbGlobalCommand(string Args, bool Wait = true, bool bShouldLogCommand = false, bool bPauseErrorParsing = false)
|
|
{
|
|
CommandUtils.ERunOptions RunOptions = CommandUtils.ERunOptions.AppMustExist | CommandUtils.ERunOptions.NoWaitForExit;
|
|
|
|
if (Log.IsVeryVerbose)
|
|
{
|
|
RunOptions |= CommandUtils.ERunOptions.AllowSpew;
|
|
}
|
|
else
|
|
{
|
|
RunOptions |= CommandUtils.ERunOptions.NoLoggingOfRunCommand;
|
|
}
|
|
|
|
if (bShouldLogCommand)
|
|
{
|
|
Log.Verbose("Running ADB Command: adb {0}", Args);
|
|
}
|
|
|
|
IProcessResult Process;
|
|
|
|
using (bPauseErrorParsing ? new ScopedSuspendECErrorParsing() : null)
|
|
{
|
|
Process = AndroidPlatform.RunAdbCommand(null, null, Args, null, RunOptions);
|
|
|
|
if (Wait)
|
|
{
|
|
Process.WaitForExit();
|
|
}
|
|
}
|
|
|
|
return Process;
|
|
}
|
|
|
|
public void AllowDeviceSleepState(bool bAllowSleep)
|
|
{
|
|
string CommandLine = "shell svc power stayon " + (bAllowSleep ? "false" : "usb");
|
|
RunAdbDeviceCommand(CommandLine, true, false, true);
|
|
}
|
|
/// <summary>
|
|
/// Enable Android permissions which would otherwise block automation with permimssion requests
|
|
/// </summary>
|
|
public void EnablePermissions(string AndroidPackageName)
|
|
{
|
|
List<string> Permissions = new List<string>{ "WRITE_EXTERNAL_STORAGE", "GET_ACCOUNTS", "RECORD_AUDIO" };
|
|
Permissions.ForEach(Permission => {
|
|
string CommandLine = string.Format("shell pm grant {0} android.permission.{1}", AndroidPackageName, Permission);
|
|
Log.Verbose(string.Format("Enabling permission: {0} {1}", AndroidPackageName, Permission));
|
|
RunAdbDeviceCommand(CommandLine, true, false, true);
|
|
});
|
|
}
|
|
|
|
public void KillRunningProcess(string AndroidPackageName)
|
|
{
|
|
Log.Verbose("{0}: Killing process '{1}' ", ToString(), AndroidPackageName);
|
|
string KillProcessCommand = string.Format("shell am force-stop {0}", AndroidPackageName);
|
|
RunAdbDeviceCommand(KillProcessCommand);
|
|
}
|
|
|
|
public Dictionary<EIntendedBaseCopyDirectory, string> GetPlatformDirectoryMappings()
|
|
{
|
|
if (LocalDirectoryMappings.Count == 0)
|
|
{
|
|
Log.Warning("Platform directory mappings have not been populated for this platform! This should be done within InstallApplication()");
|
|
}
|
|
return LocalDirectoryMappings;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// ADB key credentials, running adb-server commands (must) use same pub/private key store
|
|
/// </summary>
|
|
internal static class AdbCredentialCache
|
|
{
|
|
|
|
private static int InstanceCount = 0;
|
|
private static bool bUsingCustomKeys = false;
|
|
|
|
private static string PrivateKey;
|
|
private static string PublicKey;
|
|
|
|
private const string KeyBackupExt = ".gauntlet.bak";
|
|
|
|
private static void Reset()
|
|
{
|
|
if (InstanceCount != 0)
|
|
{
|
|
throw new AutomationException("AdbCredentialCache.Reset() called with outstanding instances");
|
|
}
|
|
|
|
PrivateKey = PublicKey = String.Empty;
|
|
bUsingCustomKeys = false;
|
|
|
|
RestoreBackupKeys();
|
|
}
|
|
|
|
public static void AddInstance(AndroidDeviceData DeviceData = null)
|
|
{
|
|
lock (Globals.MainLock)
|
|
{
|
|
string KeyPath = Globals.Params.ParseValue("adbkeys", null);
|
|
|
|
// setup key store from device data
|
|
if (String.IsNullOrEmpty(KeyPath) && DeviceData != null)
|
|
{
|
|
// checked that cached keys are the same
|
|
if (!String.IsNullOrEmpty(PrivateKey))
|
|
{
|
|
if (PrivateKey != DeviceData.privateKey)
|
|
{
|
|
throw new AutomationException("ADB device private keys must match");
|
|
}
|
|
}
|
|
|
|
if (!String.IsNullOrEmpty(PublicKey))
|
|
{
|
|
if (PublicKey != DeviceData.publicKey)
|
|
{
|
|
throw new AutomationException("ADB device public keys must match");
|
|
}
|
|
}
|
|
|
|
PrivateKey = DeviceData.privateKey;
|
|
PublicKey = DeviceData.publicKey;
|
|
|
|
if (String.IsNullOrEmpty(PublicKey) || String.IsNullOrEmpty(PrivateKey))
|
|
{
|
|
throw new AutomationException("Invalid key in device data");
|
|
}
|
|
|
|
KeyPath = Path.Combine(Globals.TempDir, "AndroidADBKeys");
|
|
|
|
if (!Directory.Exists(KeyPath))
|
|
{
|
|
Directory.CreateDirectory(KeyPath);
|
|
}
|
|
|
|
if (InstanceCount == 0)
|
|
{
|
|
byte[] data = Convert.FromBase64String(PrivateKey);
|
|
File.WriteAllText(KeyPath + "/adbkey", Encoding.UTF8.GetString(data));
|
|
|
|
data = Convert.FromBase64String(PublicKey);
|
|
File.WriteAllText(KeyPath + "/adbkey.pub", Encoding.UTF8.GetString(data));
|
|
}
|
|
|
|
}
|
|
|
|
if (InstanceCount == 0 && !String.IsNullOrEmpty(KeyPath))
|
|
{
|
|
|
|
Log.Info("Using adb keys at {0}", KeyPath);
|
|
|
|
string LocalKeyPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), ".android");
|
|
|
|
string RemoteKeyFile = Path.Combine(KeyPath, "adbkey");
|
|
string RemotePubKeyFile = Path.Combine(KeyPath, "adbkey.pub");
|
|
string LocalKeyFile = Path.Combine(LocalKeyPath, "adbkey");
|
|
string LocalPubKeyFile = Path.Combine(LocalKeyPath, "adbkey.pub");
|
|
string BackupSentry = Path.Combine(LocalKeyPath, "gauntlet.inuse");
|
|
|
|
if (File.Exists(RemoteKeyFile) == false)
|
|
{
|
|
throw new AutomationException("adbkey at {0} does not exist", KeyPath);
|
|
}
|
|
|
|
if (File.Exists(RemotePubKeyFile) == false)
|
|
{
|
|
throw new AutomationException("adbkey.pub at {0} does not exist", KeyPath);
|
|
}
|
|
|
|
if (File.Exists(BackupSentry) == false)
|
|
{
|
|
if (File.Exists(LocalKeyFile))
|
|
{
|
|
File.Copy(LocalKeyFile, LocalKeyFile + KeyBackupExt, true);
|
|
}
|
|
|
|
if (File.Exists(LocalPubKeyFile))
|
|
{
|
|
File.Copy(LocalPubKeyFile, LocalPubKeyFile + KeyBackupExt, true);
|
|
}
|
|
File.WriteAllText(BackupSentry, "placeholder");
|
|
}
|
|
|
|
File.Copy(RemoteKeyFile, LocalKeyFile, true);
|
|
File.Copy(RemotePubKeyFile, LocalPubKeyFile, true);
|
|
|
|
bUsingCustomKeys = true;
|
|
|
|
KillAdbServer();
|
|
}
|
|
|
|
InstanceCount++;
|
|
}
|
|
|
|
}
|
|
|
|
private static void KillAdbServer()
|
|
{
|
|
using (new ScopedSuspendECErrorParsing())
|
|
{
|
|
Log.Info("Running adb kill-server to refresh credentials");
|
|
TargetDeviceAndroid.RunAdbGlobalCommand("kill-server");
|
|
// killing the adb server restarts it and can surface superfluous device errors
|
|
int SleepTime = CommandUtils.IsBuildMachine ? 15000 : 5000;
|
|
Thread.Sleep(SleepTime);
|
|
}
|
|
|
|
}
|
|
|
|
public static void RemoveInstance()
|
|
{
|
|
lock (Globals.MainLock)
|
|
{
|
|
|
|
InstanceCount--;
|
|
|
|
if (InstanceCount == 0 && bUsingCustomKeys)
|
|
{
|
|
Reset();
|
|
KillAdbServer();
|
|
}
|
|
}
|
|
}
|
|
|
|
public static void RestoreBackupKeys()
|
|
{
|
|
string LocalKeyPath = Path.Combine(Environment.GetEnvironmentVariable("USERPROFILE"), ".android");
|
|
string LocalKeyFile = Path.Combine(LocalKeyPath, "adbkey");
|
|
string LocalPubKeyFile = Path.Combine(LocalKeyPath, "adbkey.pub");
|
|
string BackupSentry = Path.Combine(LocalKeyPath, "gauntlet.inuse");
|
|
|
|
if (File.Exists(BackupSentry))
|
|
{
|
|
Log.Info("Restoring original adb keys");
|
|
|
|
if (File.Exists(LocalKeyFile + KeyBackupExt))
|
|
{
|
|
File.Copy(LocalKeyFile + KeyBackupExt, LocalKeyFile, true);
|
|
File.Delete(LocalKeyFile + KeyBackupExt);
|
|
}
|
|
else
|
|
{
|
|
File.Delete(LocalKeyFile);
|
|
}
|
|
|
|
if (File.Exists(LocalPubKeyFile + KeyBackupExt))
|
|
{
|
|
File.Copy(LocalPubKeyFile + KeyBackupExt, LocalPubKeyFile, true);
|
|
File.Delete(LocalPubKeyFile + KeyBackupExt);
|
|
}
|
|
else
|
|
{
|
|
File.Delete(LocalPubKeyFile);
|
|
}
|
|
|
|
File.Delete(BackupSentry);
|
|
}
|
|
|
|
}
|
|
|
|
static AdbCredentialCache()
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} |