public override void Deploy(ProjectParams Params, DeploymentContext SC)
    {
        string DeviceArchitecture = GetBestDeviceArchitecture(Params);
        string GPUArchitecture    = GetBestGPUArchitecture(Params);

        string ApkName = GetFinalApkName(Params, SC.StageExecutables[0], true, DeviceArchitecture, GPUArchitecture);

        // make sure APK is up to date (this is fast if so)
        var Deploy = UEBuildDeploy.GetBuildDeploy(UnrealTargetPlatform.Android);

        if (!Params.Prebuilt)
        {
            string CookFlavor = SC.FinalCookPlatform.IndexOf("_") > 0 ? SC.FinalCookPlatform.Substring(SC.FinalCookPlatform.IndexOf("_")) : "";
            string SOName     = GetSONameWithoutArchitecture(Params, SC.StageExecutables[0]);
            Deploy.PrepForUATPackageOrDeploy(Params.ShortProjectName, SC.ProjectRoot, SOName, SC.LocalRoot + "/Engine", Params.Distribution, CookFlavor, true);
        }

        // now we can use the apk to get more info
        string PackageName = GetPackageInfo(ApkName, false);

        // Setup the OBB name and add the storage path (queried from the device) to it
        string        DeviceStorageQueryCommand = GetStorageQueryCommand();
        ProcessResult Result          = RunAdbCommand(Params, DeviceStorageQueryCommand, null, ERunOptions.AppMustExist);
        String        StorageLocation = Result.Output.Trim();
        string        DeviceObbName   = StorageLocation + "/" + GetDeviceObbName(ApkName);
        string        RemoteDir       = StorageLocation + "/UE4Game/" + Params.ShortProjectName;

        // determine if APK out of date
        string APKLastUpdateTime = new FileInfo(ApkName).LastWriteTime.ToString();
        bool   bNeedAPKInstall   = true;

        if (Params.IterativeDeploy)
        {
            // Check for apk installed with this package name on the device
            ProcessResult InstalledResult = RunAdbCommand(Params, "shell pm list packages " + PackageName, null, ERunOptions.AppMustExist);
            if (InstalledResult.Output.Contains(PackageName))
            {
                // See if apk is up to date on device
                InstalledResult = RunAdbCommand(Params, "shell cat " + RemoteDir + "/APKFileStamp.txt", null, ERunOptions.AppMustExist);
                if (InstalledResult.Output.StartsWith("APK: "))
                {
                    if (InstalledResult.Output.Substring(5).Trim() == APKLastUpdateTime)
                    {
                        bNeedAPKInstall = false;
                    }

                    // Stop the previously running copy (uninstall/install did this before)
                    InstalledResult = RunAdbCommand(Params, "shell am force-stop " + PackageName, null, ERunOptions.AppMustExist);
                    if (InstalledResult.Output.Contains("Error"))
                    {
                        // force-stop not supported (Android < 3.0) so check if package is actually running
                        // Note: cannot use grep here since it may not be installed on device
                        InstalledResult = RunAdbCommand(Params, "shell ps", null, ERunOptions.AppMustExist);
                        if (InstalledResult.Output.Contains(PackageName))
                        {
                            // it is actually running so use the slow way to kill it (uninstall and reinstall)
                            bNeedAPKInstall = true;
                        }
                    }
                }
            }
        }

        // install new APK if needed
        if (bNeedAPKInstall)
        {
            // try uninstalling an old app with the same identifier.
            int    SuccessCode          = 0;
            string UninstallCommandline = "uninstall " + PackageName;
            RunAndLogAdbCommand(Params, UninstallCommandline, out SuccessCode);

            // install the apk
            string InstallCommandline = "install \"" + ApkName + "\"";
            string InstallOutput      = RunAndLogAdbCommand(Params, InstallCommandline, out SuccessCode);
            int    FailureIndex       = InstallOutput.IndexOf("Failure");

            // adb install doesn't always return an error code on failure, and instead prints "Failure", followed by an error code.
            if (SuccessCode != 0 || FailureIndex != -1)
            {
                string ErrorMessage = String.Format("Installation of apk '{0}' failed", ApkName);
                if (FailureIndex != -1)
                {
                    string FailureString = InstallOutput.Substring(FailureIndex + 7).Trim();
                    if (FailureString != "")
                    {
                        ErrorMessage += ": " + FailureString;
                    }
                }

                ErrorReporter.Error(ErrorMessage, (int)ErrorCodes.Error_AppInstallFailed);
                throw new AutomationException(ErrorMessage);
            }
        }

        // update the ue4commandline.txt
        // update and deploy ue4commandline.txt
        // always delete the existing commandline text file, so it doesn't reuse an old one
        string IntermediateCmdLineFile = CombinePaths(SC.StageDirectory, "UE4CommandLine.txt");

        Project.WriteStageCommandline(IntermediateCmdLineFile, Params, SC);

        // copy files to device if we were staging
        if (SC.Stage)
        {
            // cache some strings
            string BaseCommandline = "push";

            HashSet <string> EntriesToDeploy = new HashSet <string>();

            if (Params.IterativeDeploy)
            {
                // always send UE4CommandLine.txt (it was written above after delta checks applied)
                EntriesToDeploy.Add(IntermediateCmdLineFile);

                // Add non UFS files if any to deploy
                String NonUFSManifestPath = SC.GetNonUFSDeploymentDeltaPath();
                if (File.Exists(NonUFSManifestPath))
                {
                    string NonUFSFiles = File.ReadAllText(NonUFSManifestPath);
                    foreach (string Filename in NonUFSFiles.Split('\n'))
                    {
                        if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
                        {
                            EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim()));
                        }
                    }
                }

                // Add UFS files if any to deploy
                String UFSManifestPath = SC.GetUFSDeploymentDeltaPath();
                if (File.Exists(UFSManifestPath))
                {
                    string UFSFiles = File.ReadAllText(UFSManifestPath);
                    foreach (string Filename in UFSFiles.Split('\n'))
                    {
                        if (!string.IsNullOrEmpty(Filename) && !string.IsNullOrWhiteSpace(Filename))
                        {
                            EntriesToDeploy.Add(CombinePaths(SC.StageDirectory, Filename.Trim()));
                        }
                    }
                }

                // For now, if too many files may be better to just push them all
                if (EntriesToDeploy.Count > 500)
                {
                    // make sure device is at a clean state
                    RunAdbCommand(Params, "shell rm -r " + RemoteDir);

                    EntriesToDeploy.Clear();
                    EntriesToDeploy.TrimExcess();
                    EntriesToDeploy.Add(SC.StageDirectory);
                }
            }
            else
            {
                // make sure device is at a clean state
                RunAdbCommand(Params, "shell rm -r " + RemoteDir);

                // Copy UFS files..
                string[] Files = Directory.GetFiles(SC.StageDirectory, "*", SearchOption.AllDirectories);
                System.Array.Sort(Files);

                // Find all the files we exclude from copying. And include
                // the directories we need to individually copy.
                HashSet <string>   ExcludedFiles = new HashSet <string>();
                SortedSet <string> IndividualCopyDirectories
                    = new SortedSet <string>((IComparer <string>) new LongestFirst());
                foreach (string Filename in Files)
                {
                    bool Exclude = false;
                    // Don't push the apk, we install it
                    Exclude |= Path.GetExtension(Filename).Equals(".apk", StringComparison.InvariantCultureIgnoreCase);
                    // For excluded files we add the parent dirs to our
                    // tracking of stuff to individually copy.
                    if (Exclude)
                    {
                        ExcludedFiles.Add(Filename);
                        // We include all directories up to the stage root in having
                        // to individually copy the files.
                        for (string FileDirectory = Path.GetDirectoryName(Filename);
                             !FileDirectory.Equals(SC.StageDirectory);
                             FileDirectory = Path.GetDirectoryName(FileDirectory))
                        {
                            if (!IndividualCopyDirectories.Contains(FileDirectory))
                            {
                                IndividualCopyDirectories.Add(FileDirectory);
                            }
                        }
                        if (!IndividualCopyDirectories.Contains(SC.StageDirectory))
                        {
                            IndividualCopyDirectories.Add(SC.StageDirectory);
                        }
                    }
                }

                // The directories are sorted above in "deepest" first. We can
                // therefore start copying those individual dirs which will
                // recreate the tree. As the subtrees will get copied at each
                // possible individual level.
                foreach (string DirectoryName in IndividualCopyDirectories)
                {
                    string[] Entries
                        = Directory.GetFileSystemEntries(DirectoryName, "*", SearchOption.TopDirectoryOnly);
                    foreach (string Entry in Entries)
                    {
                        // We avoid excluded files and the individual copy dirs
                        // (the individual copy dirs will get handled as we iterate).
                        if (ExcludedFiles.Contains(Entry) || IndividualCopyDirectories.Contains(Entry))
                        {
                            continue;
                        }
                        else
                        {
                            EntriesToDeploy.Add(Entry);
                        }
                    }
                }

                if (EntriesToDeploy.Count == 0)
                {
                    EntriesToDeploy.Add(SC.StageDirectory);
                }
            }

            // We now have a minimal set of file & dir entries we need
            // to deploy. Files we deploy will get individually copied
            // and dirs will get the tree copies by default (that's
            // what ADB does).
            HashSet <ProcessResult> DeployCommands = new HashSet <ProcessResult>();
            foreach (string Entry in EntriesToDeploy)
            {
                string FinalRemoteDir = RemoteDir;
                string RemotePath     = Entry.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
                string Commandline    = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, Entry, RemotePath);
                // We run deploy commands in parallel to maximize the connection
                // throughput.
                DeployCommands.Add(
                    RunAdbCommand(Params, Commandline, null,
                                  ERunOptions.Default | ERunOptions.NoWaitForExit));
                // But we limit the parallel commands to avoid overwhelming
                // memory resources.
                if (DeployCommands.Count == DeployMaxParallelCommands)
                {
                    while (DeployCommands.Count > DeployMaxParallelCommands / 2)
                    {
                        Thread.Sleep(1);
                        DeployCommands.RemoveWhere(
                            delegate(ProcessResult r)
                        {
                            return(r.HasExited);
                        });
                    }
                }
            }
            foreach (ProcessResult deploy_result in DeployCommands)
            {
                deploy_result.WaitForExit();
            }

            // delete the .obb file, since it will cause nothing we just deployed to be used
            RunAdbCommand(Params, "shell rm " + DeviceObbName);
        }
        else if (SC.Archive)
        {
            // deploy the obb if there is one
            string ObbPath = Path.Combine(SC.StageDirectory, GetFinalObbName(ApkName));
            if (File.Exists(ObbPath))
            {
                // cache some strings
                string BaseCommandline = "push";

                string Commandline = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, ObbPath, DeviceObbName);
                RunAdbCommand(Params, Commandline);
            }
        }
        else
        {
            // cache some strings
            string BaseCommandline = "push";

            string FinalRemoteDir = RemoteDir;

            /*
             * // handle the special case of the UE4Commandline.txt when using content only game (UE4Game)
             * if (!Params.IsCodeBasedProject)
             * {
             *      FinalRemoteDir = "/mnt/sdcard/UE4Game";
             * }
             */

            string RemoteFilename = IntermediateCmdLineFile.Replace(SC.StageDirectory, FinalRemoteDir).Replace("\\", "/");
            string Commandline    = string.Format("{0} \"{1}\" \"{2}\"", BaseCommandline, IntermediateCmdLineFile, RemoteFilename);
            RunAdbCommand(Params, Commandline);
        }

        // write new timestamp for APK (do it here since RemoteDir will now exist)
        if (bNeedAPKInstall)
        {
            int SuccessCode = 0;
            RunAndLogAdbCommand(Params, "shell \"echo 'APK: " + APKLastUpdateTime + "' > " + RemoteDir + "/APKFileStamp.txt\"", out SuccessCode);
        }
    }