You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Mostly a find/replace, though I have looked through the changes and attempted to update references to other things as necessary (eg. renaming IOS plist files for IOS). I'm not set up to test on any platforms other than windows, and was hoping to get your blessing to submit and give QA enough time as possible to uncover issues before the next milestone release. Particular things that I know I'm not sure about: - Android references /UE4Game/ paths everywhere (for paths on device, I think). I have no idea if I've got them all. - I've renamed the iOS mobileprovisions, but I don't know if they need regenerating for the new app name. - Likewise, not sure what needs to be updated for icon bundles on iOS. Things that have not been changed: - Windows still uses IDI_UE4ICON for its icon - UE4CommandLine.txt - There's still a UE4Game module which is used by content-only projects #rb none [CL 14301890 by Ben Marsh in ue5-main branch]
1389 lines
40 KiB
C#
1389 lines
40 KiB
C#
// Copyright 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;
|
|
// The activity has exited, make sure entire activity log has been captured, sleep to allow time for the log to flush
|
|
Thread.Sleep(5000);
|
|
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))
|
|
{
|
|
|
|
Log.Verbose("Deleting {0}", 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)
|
|
try
|
|
{
|
|
Utils.SystemHelpers.MarkDirectoryForCleanup(Install.AndroidDevice.LocalCachePath);
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
Log.Warning("Exception marking directory for cleanup {0}", Ex.Message);
|
|
}
|
|
|
|
string LocalSaved = Path.Combine(Install.AndroidDevice.LocalCachePath, "Saved");
|
|
Log.Verbose("Creating {0}", LocalSaved);
|
|
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, bShouldLogCommand: Log.IsVerbose);
|
|
|
|
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", bShouldLogCommand: Log.IsVerbose);
|
|
|
|
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 + "/UnrealGame/" + AppConfig.ProjectName;
|
|
|
|
// if using a non-bulk test configuration, implies com.epicgames.ue4.GameActivity.bUseExternalFilesDir = true
|
|
// @todo: query this from the apk?
|
|
if (AppConfig.Configuration == UnrealTargetConfiguration.Test && ((Build.Flags & BuildFlags.NotBulk) == BuildFlags.NotBulk))
|
|
{
|
|
RemoteDir = StorageLocation + "/Android/data/" + Build.AndroidPackageName + "/files/UnrealGame/" + 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)
|
|
{
|
|
string[] Delimiters = { "\r\n", "\n" };
|
|
string[] CurrentRemoteFileList = AdbResult.Output.Split(Delimiters, StringSplitOptions.RemoveEmptyEntries);
|
|
for (int i = 0; i < CurrentRemoteFileList.Length; ++i)
|
|
{
|
|
CurrentRemoteFileList[i] = CurrentRemoteFileList[i].Trim();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} |