static void BuildAndRun()
    {
        if (!PXR_ADBTool.GetInstance().CheckADBDevices())
        {
            return;
        }

        apkOutputSuccessful = null;
        syncCancelToken     = null;
        gradleBuildProcess  = null;

        gradleTempExport = Path.Combine(Path.Combine(Application.dataPath, "../Temp"), "PXRGradleTempExport");
        gradleExport     = Path.Combine(Path.Combine(Application.dataPath, "../Temp"), "PXRGradleExport");
        if (!Directory.Exists(gradleExport))
        {
            Directory.CreateDirectory(gradleExport);
        }

        var buildResult = UnityBuildPlayer();

        if (buildResult.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
        {
            applicationIdentifier = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
#if UNITY_2019_3_OR_NEWER
            productName = "launcher";
#else
            productName = Application.productName;
#endif

            BuildRun();
        }
    }
Beispiel #2
0
    public static bool IsInstalledAPP()
    {
        if (!PXR_ADBTool.GetInstance().IsReady())
        {
            return(false);
        }

        string matchedPackageList, error;
        var    packageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);

        string[] packageCheckCommand = new string[] { "-d shell pm list package", packageName };
        if (PXR_ADBTool.GetInstance().RunCommand(packageCheckCommand, null, out matchedPackageList, out error) == 0)
        {
            if (string.IsNullOrEmpty(matchedPackageList))
            {
                return(false);
            }

            if (!matchedPackageList.Contains("package:" + packageName + "\r\n"))
            {
                return(false);
            }

            string[] dumpPackageInfoCommand = new string[] { "-d shell dumpsys package", packageName };
            string   packageInfo;
            if (PXR_ADBTool.GetInstance().RunCommand(dumpPackageInfoCommand, null, out packageInfo, out error) == 0 &&
                !string.IsNullOrEmpty(packageInfo) &&
                packageInfo.Contains(SQP_APK_VERSION))
            {
                return(true);
            }
            return(false);
        }
        return(false);
    }
 static void OpenSceneQuickPreviewUI()
 {
     GetWindow <PXR_SceneQucikPreviewEW>();
     PXR_ADBTool.GetInstance().CheckADBDevices(log =>
     {
         if (!string.IsNullOrEmpty(log))
         {
             PrintError(log);
         }
     });
     EditorBuildSettings.sceneListChanged += PXR_BuildToolManager.GetScenesEnabled;
 }
    static void BuildAndRun()
    {
        GetWindow(typeof(PXR_BuildAndRunEW));
        showCancel     = false;
        buildFailed    = false;
        totalBuildTime = 0;

        InitializeProgressBar(NUM_BUILD_AND_RUN_STEPS);
        IncrementProgressBar("Exporting Unity Project . . .");

        if (!PXR_ADBTool.GetInstance().CheckADBDevices(log => {}))
        {
            buildFailed = true;
            return;
        }

        apkOutputSuccessful = null;
        syncCancelToken     = null;
        gradleBuildProcess  = null;

        UnityEngine.Debug.Log("PXRBuild: Starting Unity build ...");

        SetupDirectories();

        // 1. Get scenes to build in Unity, and export gradle project
        var buildResult = UnityBuildPlayer();

        if (buildResult.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
        {
            totalBuildTime += buildResult.summary.totalTime.TotalSeconds;

            // Set static variables so build thread has updated data
            showCancel            = true;
            applicationIdentifier = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
#if UNITY_2019_3_OR_NEWER
            productName = "launcher";
#else
            productName = Application.productName;
#endif

            BuildRun();
            return;
        }
        else if (buildResult.summary.result == UnityEditor.Build.Reporting.BuildResult.Cancelled)
        {
            UnityEngine.Debug.Log("Build canceled.");
        }
        else
        {
            UnityEngine.Debug.Log("Build failed.");
        }
        buildFailed = true;
    }
Beispiel #5
0
    public static bool RestartApp()
    {
        if (!PXR_ADBTool.GetInstance().IsReady())
        {
            return(false);
        }

        string output, error;

        string[] appStartCommand = { "-d shell", "am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -S -f 0x10200000 -n", PXR_PathHelper.GetPlayerActivityName() };
        if (PXR_ADBTool.GetInstance().RunCommand(appStartCommand, null, out output, out error) == 0)
        {
            PXR_SceneQuickPreviewEW.PrintLog("App " + " Restart Success!", PXR_SceneQuickPreviewEW.LogType.Success);
            return(true);
        }

        string completeError = "PXRLog Failed to restart App. Try restarting it manually through the device.\n" + (string.IsNullOrEmpty(error) ? output : error);

        Debug.LogError(completeError);
        return(false);
    }
Beispiel #6
0
    public static bool UninstallAPP()
    {
        PXR_SceneQuickPreviewEW.PrintLog("Uninstalling Application . . .", PXR_SceneQuickPreviewEW.LogType.Normal);

        if (!PXR_ADBTool.GetInstance().IsReady())
        {
            return(false);
        }

        string output, error;
        string appPackageName = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);

        string[] appStartCommand = { "-d shell", "pm uninstall", appPackageName };
        if (PXR_ADBTool.GetInstance().RunCommand(appStartCommand, null, out output, out error) == 0)
        {
            PXR_SceneQuickPreviewEW.PrintLog("App package " + appPackageName + " is uninstalled.", PXR_SceneQuickPreviewEW.LogType.Success);
            return(true);
        }

        PXR_SceneQuickPreviewEW.PrintLog("Failed to uninstall APK.", PXR_SceneQuickPreviewEW.LogType.Error);
        return(false);
    }
    void CancelGradleBuild()
    {
        Process cancelGradleProcess = new Process();
        string  arguments           = "-Xmx1024m -classpath \"" + PXR_ADBTool.GetInstance().GetGradlePath() +
                                      "\" org.gradle.launcher.GradleMain --stop";
        var processInfo = new System.Diagnostics.ProcessStartInfo
        {
            WindowStyle            = System.Diagnostics.ProcessWindowStyle.Normal,
            FileName               = PXR_ADBTool.GetInstance().GetJDKPath(),
            Arguments              = arguments,
            RedirectStandardInput  = true,
            UseShellExecute        = false,
            CreateNoWindow         = true,
            RedirectStandardError  = true,
            RedirectStandardOutput = true,
        };

        cancelGradleProcess.StartInfo           = processInfo;
        cancelGradleProcess.EnableRaisingEvents = true;

        cancelGradleProcess.OutputDataReceived += new DataReceivedEventHandler(
            (s, e) =>
        {
            if (e != null && e.Data != null && e.Data.Length != 0)
            {
                UnityEngine.Debug.LogFormat("Gradle: {0}", e.Data);
            }
        }
            );

        apkOutputSuccessful = false;

        cancelGradleProcess.Start();
        cancelGradleProcess.BeginOutputReadLine();
        cancelGradleProcess.WaitForExit();

        buildFailed = true;
    }
Beispiel #8
0
 static void OpenSceneQuickPreviewUI()
 {
     GetWindow <PXR_SceneQuickPreviewEW>();
     PXR_ADBTool.GetInstance().CheckADBDevices();
     EditorBuildSettings.sceneListChanged += PXR_BuildToolManager.GetScenesEnabled;
 }
Beispiel #9
0
    public static void BuildScenes(bool forceRestart)
    {
        if (!PXR_ADBTool.GetInstance().IsReady())
        {
            return;
        }

        GetScenesEnabled();

        remoteSceneCache = EXTERNAL_STORAGE_PATH + "/" + PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android)
                           + "/cache/scenes";

        Dictionary <string, string>         assetInSceneBundle = new Dictionary <string, string>();
        List <AssetBundleBuild>             assetBundleBuilds  = new List <AssetBundleBuild>();
        Dictionary <string, List <string> > extToAssetList     = new Dictionary <string, List <string> >();

        string[] resDirectories = Directory.GetDirectories("Assets", "Resources", SearchOption.AllDirectories).ToArray();

        if (resDirectories.Length > 0)
        {
            string[] resAssetPaths = AssetDatabase.FindAssets("", resDirectories).Select(x => AssetDatabase.GUIDToAssetPath(x)).ToArray();
            ProcessAssets(resAssetPaths, "resources", ref assetInSceneBundle, ref extToAssetList);

            AssetBundleBuild resBundle = new AssetBundleBuild();
            resBundle.assetNames      = assetInSceneBundle.Keys.ToArray();
            resBundle.assetBundleName = PXR_SQPLoader.RESOURCE_BUNDLE_NAME;
            assetBundleBuilds.Add(resBundle);
        }

        foreach (var scene in buildSceneInfoList)
        {
            string[] assetDependencies = AssetDatabase.GetDependencies(scene.scenePath);
            ProcessAssets(assetDependencies, scene.sceneName, ref assetInSceneBundle, ref extToAssetList);

            string[] sceneAsset = new string[1] {
                scene.scenePath
            };
            AssetBundleBuild sceneBuild = new AssetBundleBuild();
            sceneBuild.assetBundleName = "scene_" + scene.sceneName;
            sceneBuild.assetNames      = sceneAsset;
            assetBundleBuilds.Add(sceneBuild);
        }

        foreach (string ext in extToAssetList.Keys)
        {
            int assetCount = extToAssetList[ext].Count;
            int numChunks  = (assetCount + BUNDLE_CHUNK_SIZE - 1) / BUNDLE_CHUNK_SIZE;
            for (int i = 0; i < numChunks; i++)
            {
                List <string> assetChunkList;
                if (i == numChunks - 1)
                {
                    int size = BUNDLE_CHUNK_SIZE - (numChunks * BUNDLE_CHUNK_SIZE - assetCount);
                    assetChunkList = extToAssetList[ext].GetRange(i * BUNDLE_CHUNK_SIZE, size);
                }
                else
                {
                    assetChunkList = extToAssetList[ext].GetRange(i * BUNDLE_CHUNK_SIZE, BUNDLE_CHUNK_SIZE);
                }
                AssetBundleBuild build = new AssetBundleBuild();
                build.assetBundleName = "asset_" + ext + i;
                build.assetNames      = assetChunkList.ToArray();
                assetBundleBuilds.Add(build);
            }
        }

        // Build asset bundles
        BuildPipeline.BuildAssetBundles(PXR_DirectorySyncer.CreateDirectory(SQP_BUNDLE_PATH, SQP_SCENE_BUNDLE), assetBundleBuilds.ToArray(),
                                        BuildAssetBundleOptions.UncompressedAssetBundle, BuildTarget.Android);

        string tempDirectory = PXR_DirectorySyncer.CreateDirectory(SQP_BUNDLE_PATH, "Temp");

        string absoluteTempPath = Path.Combine(Path.Combine(Application.dataPath, ".."), tempDirectory);

        if (!PullSceneBundles(absoluteTempPath, remoteSceneCache))
        {
            return;
        }

        string sceneLoadDataPath = Path.Combine(tempDirectory, SCENE_LOAD_DATA_NAME);

        if (File.Exists(sceneLoadDataPath))
        {
            File.Delete(sceneLoadDataPath);
        }

        StreamWriter writer = new StreamWriter(sceneLoadDataPath, true);

        long unixTime = (int)(DateTimeOffset.UtcNow.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;

        writer.WriteLine(unixTime.ToString());
        for (int i = 0; i < buildSceneInfoList.Count; i++)
        {
            writer.WriteLine(Path.GetFileNameWithoutExtension(buildSceneInfoList[i].scenePath));
        }

        writer.Close();

        string absoluteSceneLoadDataPath = Path.Combine(absoluteTempPath, SCENE_LOAD_DATA_NAME);

        string[] pushCommand = { "-d push", "\"" + absoluteSceneLoadDataPath + "\"", "\"" + remoteSceneCache + "\"" };
        string   output, error;

        if (PXR_ADBTool.GetInstance().RunCommand(pushCommand, null, out output, out error) != 0)
        {
            PXR_SceneQuickPreviewEW.PrintLog(string.IsNullOrEmpty(error) ? output : error, PXR_SceneQuickPreviewEW.LogType.Error);
            return;
        }

        if (!IsInstalledAPP())
        {
            InstallAPP();
        }


        if (forceRestart)
        {
            RestartApp();
            return;
        }

        PXR_SceneQuickPreviewEW.PrintLog("Build Scenes.", PXR_SceneQuickPreviewEW.LogType.Success);
    }
Beispiel #10
0
    private static bool PullSceneBundles(string absoluteTempPath, string externalSceneCache)
    {
        List <string> bundlesToTransfer = new List <string>();
        string        manifestFilePath  = externalSceneCache + "/" + SQP_SCENE_BUNDLE;

        string[] pullManifestCommand = { "-d pull", "\"" + manifestFilePath + "\"", "\"" + absoluteTempPath + "\"" };

        string output, error;

        if (PXR_ADBTool.GetInstance().RunCommand(pullManifestCommand, null, out output, out error) == 0)
        {
            AssetBundle remoteBundle = AssetBundle.LoadFromFile(Path.Combine(absoluteTempPath, SQP_SCENE_BUNDLE));
            if (remoteBundle == null)
            {
                PXR_SceneQuickPreviewEW.PrintLog("Failed to load remote asset bundle manifest file.", PXR_SceneQuickPreviewEW.LogType.Error);
                return(false);
            }
            AssetBundleManifest remoteManifest = remoteBundle.LoadAsset <AssetBundleManifest>("AssetBundleManifest");

            Dictionary <string, Hash128> remoteBundleToHash = new Dictionary <string, Hash128>();
            if (remoteManifest != null)
            {
                string[] assetBundles = remoteManifest.GetAllAssetBundles();
                foreach (string bundleName in assetBundles)
                {
                    remoteBundleToHash[bundleName] = remoteManifest.GetAssetBundleHash(bundleName);
                }
            }
            remoteBundle.Unload(true);

            AssetBundle localBundle = AssetBundle.LoadFromFile(SQP_BUNDLE_PATH + "\\" + SQP_SCENE_BUNDLE
                                                               + "\\" + SQP_SCENE_BUNDLE);
            if (localBundle == null)
            {
                PXR_SceneQuickPreviewEW.PrintLog("Failed to load local asset bundle manifest file.", PXR_SceneQuickPreviewEW.LogType.Error);
                return(false);
            }
            AssetBundleManifest localManifest = localBundle.LoadAsset <AssetBundleManifest>("AssetBundleManifest");

            if (localManifest != null)
            {
                Hash128 zeroHash = new Hash128(0, 0, 0, 0);

                string relativeSceneBundlesPath = Path.Combine(SQP_BUNDLE_PATH, SQP_SCENE_BUNDLE);
                bundlesToTransfer.Add(Path.Combine(relativeSceneBundlesPath, SQP_SCENE_BUNDLE));
                string[] assetBundles = localManifest.GetAllAssetBundles();
                foreach (string bundleName in assetBundles)
                {
                    if (!remoteBundleToHash.ContainsKey(bundleName))
                    {
                        bundlesToTransfer.Add(Path.Combine(relativeSceneBundlesPath, bundleName));
                    }
                    else
                    {
                        if (remoteBundleToHash[bundleName] != localManifest.GetAssetBundleHash(bundleName))
                        {
                            bundlesToTransfer.Add(Path.Combine(relativeSceneBundlesPath, bundleName));
                        }
                        remoteBundleToHash[bundleName] = zeroHash;
                    }
                }

                PXR_SceneQuickPreviewEW.PrintLog(bundlesToTransfer.Count + " dirty bundle(s) will be transferred.\n", PXR_SceneQuickPreviewEW.LogType.Normal);
            }
        }
        else
        {
            if (output.Contains("does not exist") || output.Contains("No such file or directory"))
            {
                PXR_SceneQuickPreviewEW.PrintLog("Manifest file not found. Transferring all bundles . . . ", PXR_SceneQuickPreviewEW.LogType.Normal);

                string[] mkdirCommand = { "-d shell", "mkdir -p", "\"" + externalSceneCache + "\"" };
                if (PXR_ADBTool.GetInstance().RunCommand(mkdirCommand, null, out output, out error) == 0)
                {
                    string absoluteSceneBundlePath = Path.Combine(Path.Combine(Application.dataPath, ".."),
                                                                  Path.Combine(SQP_BUNDLE_PATH, SQP_SCENE_BUNDLE));

                    string[] assetBundlePaths = Directory.GetFiles(absoluteSceneBundlePath);
                    if (assetBundlePaths.Length == 0)
                    {
                        PXR_SceneQuickPreviewEW.PrintLog("Failed to locate scene bundles to transfer.", PXR_SceneQuickPreviewEW.LogType.Error);
                        return(false);
                    }
                    foreach (string path in assetBundlePaths)
                    {
                        if (!path.Contains(".manifest"))
                        {
                            bundlesToTransfer.Add(path);
                        }
                    }
                }
            }
        }

        if (!string.IsNullOrEmpty(error) || output.Contains("error"))
        {
            PXR_SceneQuickPreviewEW.PrintLog(string.IsNullOrEmpty(error) ? output : error, PXR_SceneQuickPreviewEW.LogType.Error);
            return(false);
        }

        foreach (string bundle in bundlesToTransfer)
        {
            string   absoluteBundlePath = Path.Combine(Path.Combine(Application.dataPath, ".."), bundle);
            string[] pushBundleCommand  = { "-d push", "\"" + absoluteBundlePath + "\"", "\"" + externalSceneCache + "\"" };
            PXR_ADBTool.GetInstance().RunCommandAsync(pushBundleCommand, null);
        }

        return(true);
    }
    public static bool DeployAPK()
    {
        if (!PXR_ADBTool.GetInstance().IsReady())
        {
            return(false);
        }

        string gradleExportFolder = Path.Combine(Path.Combine(gradleExport, productName), "build\\outputs\\apk\\debug");

        gradleExportFolder = gradleExportFolder.Replace("/", "\\");
        if (!Directory.Exists(gradleExportFolder))
        {
            return(false);
        }

        var apkPathLocal = Path.Combine(gradleExportFolder, productName + "-debug.apk");

        if (!File.Exists(apkPathLocal))
        {
            return(false);
        }

        string output, error;

        string[] mkdirCommand = { "-d shell", "mkdir -p", "/data/local/tmp" };
        if (PXR_ADBTool.GetInstance().RunCommand(mkdirCommand, null, out output, out error) != 0)
        {
            return(false);
        }

        var timerStart = DateTime.Now;

        string[] pushCommand = { "-d push", "\"" + apkPathLocal + "\"", "/data/local/tmp" };
        if (PXR_ADBTool.GetInstance().RunCommand(pushCommand, null, out output, out error) != 0)
        {
            return(false);
        }

        TimeSpan pushTime      = System.DateTime.Now - timerStart;
        bool     trivialPush   = pushTime.TotalSeconds < 4.0f;
        long?    apkSize       = (trivialPush ? (long?)null : new System.IO.FileInfo(apkPathLocal).Length);
        double?  transferSpeed = (apkSize / pushTime.TotalSeconds) / 1048576;
        bool     informLog     = transferSpeed.HasValue && transferSpeed.Value < 25.0f;

        string apkPath = "/data/local/tmp" + "/" + productName + "-debug.apk";

        apkPath = apkPath.Replace(" ", "\\ ");
        string[] installCommand = { "-d shell", "pm install -r", apkPath };

        timerStart = DateTime.Now;
        if (PXR_ADBTool.GetInstance().RunCommand(installCommand, null, out output, out error) != 0)
        {
            return(false);
        }
        TimeSpan installTime = System.DateTime.Now - timerStart;

#if UNITY_2019_3_OR_NEWER
        string playerActivityName = "\"" + applicationIdentifier + "/com.unity3d.player.UnityPlayerActivity\"";
#else
        string playerActivityName = "\"" + applicationIdentifier + "/" + applicationIdentifier + ".UnityPlayerActivity\"";
#endif
        string[] appStartCommand = { "-d shell", "am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -S -f 0x10200000 -n", playerActivityName };
        if (PXR_ADBTool.GetInstance().RunCommand(appStartCommand, null, out output, out error) != 0)
        {
            return(false);
        }
        UnityEngine.Debug.Log("PXRLog Application Start Success");

        if (informLog)
        {
            return(true);
        }
        return(false);
    }
    private static bool BuildGradleProject()
    {
        gradleBuildProcess = new Process();
        string arguments = "-Xmx4096m -classpath \"" + PXR_ADBTool.GetInstance().GetGradlePath() +
                           "\" org.gradle.launcher.GradleMain assembleDebug -x validateSigningDebug --profile";

#if UNITY_2019_3_OR_NEWER
        var gradleProjectPath = gradleExport;
#else
        var gradleProjectPath = Path.Combine(gradleExport, productName);
#endif

        var processInfo = new ProcessStartInfo
        {
            WorkingDirectory       = gradleProjectPath,
            WindowStyle            = ProcessWindowStyle.Normal,
            FileName               = PXR_ADBTool.GetInstance().GetJDKPath(),
            Arguments              = arguments,
            RedirectStandardInput  = true,
            UseShellExecute        = false,
            CreateNoWindow         = true,
            RedirectStandardError  = true,
            RedirectStandardOutput = true,
        };

        gradleBuildProcess.StartInfo           = processInfo;
        gradleBuildProcess.EnableRaisingEvents = true;

        DateTime gradleStartTime = System.DateTime.Now;
        DateTime gradleEndTime   = System.DateTime.MinValue;

        gradleBuildProcess.Exited += (s, e) =>
        {
            UnityEngine.Debug.Log("PXRLog Gradle: Exited");
        };

        gradleBuildProcess.OutputDataReceived += new DataReceivedEventHandler(
            (s, e) =>
        {
            if (e != null && e.Data != null &&
                e.Data.Length != 0 &&
                (e.Data.Contains("BUILD") || e.Data.StartsWith("See the profiling report at:")))
            {
                UnityEngine.Debug.LogFormat("PXRLog Gradle: {0}", e.Data);
                if (e.Data.Contains("SUCCESSFUL"))
                {
                    UnityEngine.Debug.LogFormat("PXRLog APK Build Completed: {0}",
                                                Path.Combine(Path.Combine(gradleProjectPath, "build\\outputs\\apk\\debug"), productName + "-debug.apk").Replace("/", "\\"));
                    if (!apkOutputSuccessful.HasValue)
                    {
                        apkOutputSuccessful = true;
                    }
                    gradleEndTime = System.DateTime.Now;
                }
                else if (e.Data.Contains("FAILED"))
                {
                    apkOutputSuccessful = false;
                }
            }
        }
            );

        gradleBuildProcess.ErrorDataReceived += new DataReceivedEventHandler(
            (s, e) =>
        {
            if (e != null && e.Data != null &&
                e.Data.Length != 0)
            {
                UnityEngine.Debug.LogErrorFormat("Gradle: {0}", e.Data);
            }
            apkOutputSuccessful = false;
        }
            );

        gradleBuildProcess.Start();
        gradleBuildProcess.BeginOutputReadLine();
        gradleBuildProcess.WaitForExit();

        Stopwatch timeout = new Stopwatch();
        timeout.Start();
        while (apkOutputSuccessful == null)
        {
            if (timeout.ElapsedMilliseconds > 5000)
            {
                UnityEngine.Debug.LogError("PXRLog Gradle has exited unexpectedly.");
                apkOutputSuccessful = false;
            }
            Thread.Sleep(100);
        }

        return(apkOutputSuccessful.HasValue && apkOutputSuccessful.Value);
    }
    public static bool DeployAPK()
    {
        // Create new instance of ADB Tool
        if (!PXR_ADBTool.GetInstance().IsReady())
        {
            UnityEngine.Debug.LogError("Could not find the ADB executable in the specified Android SDK directory.");
            return(false);
        }
        string apkPathLocal;
        string gradleExportFolder = Path.Combine(Path.Combine(gradleExport, productName), "build\\outputs\\apk\\debug");

        // Check to see if gradle output directory exists
        gradleExportFolder = gradleExportFolder.Replace("/", "\\");
        if (!Directory.Exists(gradleExportFolder))
        {
            UnityEngine.Debug.LogError("Could not find the gradle project at the expected path: " + gradleExportFolder);
            return(false);
        }

        // Search for output APK in gradle output directory
        apkPathLocal = Path.Combine(gradleExportFolder, productName + "-debug.apk");
        if (!File.Exists(apkPathLocal))
        {
            UnityEngine.Debug.LogError(string.Format("Could not find {0} in the gradle project.", productName + "-debug.apk"));
            return(false);
        }

        string   output, error;
        DateTime timerStart;

        // Ensure that the temp directory is on the device by making it
        IncrementProgressBar("Making Temp directory on device");
        string[] mkdirCommand = { "-d shell", "mkdir -p", REMOTE_APK_PATH };
        if (PXR_ADBTool.GetInstance().RunCommand(mkdirCommand, null, out output, out error) != 0)
        {
            return(false);
        }

        // Push APK to device, also time how long it takes
        timerStart = DateTime.Now;
        IncrementProgressBar("Pushing APK to device . . .");
        string[] pushCommand = { "-d push", "\"" + apkPathLocal + "\"", REMOTE_APK_PATH };
        if (PXR_ADBTool.GetInstance().RunCommand(pushCommand, null, out output, out error) != 0)
        {
            return(false);
        }

        // Calculate the transfer speed and determine if user is using USB 2.0 or 3.0
        TimeSpan pushTime      = System.DateTime.Now - timerStart;
        bool     trivialPush   = pushTime.TotalSeconds < TRANSFER_SPEED_CHECK_THRESHOLD;
        long?    apkSize       = (trivialPush ? (long?)null : new System.IO.FileInfo(apkPathLocal).Length);
        double?  transferSpeed = (apkSize / pushTime.TotalSeconds) / BYTES_TO_MEGABYTES;
        bool     informLog     = transferSpeed.HasValue && transferSpeed.Value < USB_TRANSFER_SPEED_THRES;

        UnityEngine.Debug.Log("PXR ADB Tool: Push Success");

        // Install the APK package on the device
        IncrementProgressBar("Installing APK . . .");
        string apkPath = REMOTE_APK_PATH + "/" + productName + "-debug.apk";

        apkPath = apkPath.Replace(" ", "\\ ");
        string[] installCommand = { "-d shell", "pm install -r", apkPath };

        timerStart = DateTime.Now;
        if (PXR_ADBTool.GetInstance().RunCommand(installCommand, null, out output, out error) != 0)
        {
            return(false);
        }
        TimeSpan installTime = System.DateTime.Now - timerStart;

        UnityEngine.Debug.Log("PXR ADB Tool: Install Success");

        // Start the application on the device
        IncrementProgressBar("Launching application on device . . .");
#if UNITY_2019_3_OR_NEWER
        string playerActivityName = "\"" + applicationIdentifier + "/com.unity3d.player.UnityPlayerActivity\"";
#else
        string playerActivityName = "\"" + applicationIdentifier + "/" + applicationIdentifier + ".UnityPlayerActivity\"";
#endif
        string[] appStartCommand = { "-d shell", "am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -S -f 0x10200000 -n", playerActivityName };
        if (PXR_ADBTool.GetInstance().RunCommand(appStartCommand, null, out output, out error) != 0)
        {
            return(false);
        }
        UnityEngine.Debug.Log("PXR ADB Tool: Application Start Success");

        // Send back metrics on push and install steps
        IncrementProgressBar("Success!");

        // If the user is using a USB 2.0 cable, inform them about improved transfer speeds and estimate time saved
        if (informLog)
        {
            var usb3Time = apkSize.Value / (USB_3_TRANSFER_SPEED * BYTES_TO_MEGABYTES);
            UnityEngine.Debug.Log(string.Format("Build has detected slow transfer speeds. A USB 3.0 cable is recommended to reduce the time it takes to deploy your project by approximatly {0:0.0} seconds", pushTime.TotalSeconds - usb3Time));
            return(true);
        }
        return(false);
    }