Ejemplo n.º 1
0
        private static void CreateClassesJar(string jarPath, string tempDirectoryPath)
        {
            var javaPath = Path.Combine(
                tempDirectoryPath, "GoogleArCoreCustomizedManifestEmptyClass.java");

            using (FileStream fs = File.Create(javaPath))
            {
                byte[] info = new UTF8Encoding(true).GetBytes(
                    "public class GoogleArCoreCustomizedManifestEmptyClass {}");
                fs.Write(info, 0, info.Length);
            }

            var fileListBuilder = new StringBuilder();

            foreach (var filePath in Directory.GetFiles(tempDirectoryPath))
            {
                fileListBuilder.AppendFormat(" {0}", Path.GetFileName(filePath));
            }

            string command = string.Format(
                "cf classes.jar {0}", fileListBuilder.ToString());
            string output;
            string errors;

            ShellHelper.RunCommand(jarPath, command, out output, out errors);

            if (!string.IsNullOrEmpty(errors))
            {
                throw new BuildFailedException(
                          string.Format(
                              "Error creating classes.jar for manifest: {0}", errors));
            }

            File.Delete(javaPath);
        }
        private static void WriteCommand(string program, string arguments, StreamWriter writer)
        {
            if (string.IsNullOrEmpty(program))
            {
                writer.WriteLine("error: program path was null");
            }
            else
            {
                string stdOut;
                string stdErr;

                ShellHelper.RunCommand(program, arguments, out stdOut, out stdErr);

                if (!string.IsNullOrEmpty(stdOut))
                {
                    writer.WriteLine(stdOut);
                }

                if (!string.IsNullOrEmpty(stdErr))
                {
                    writer.WriteLine(stdErr);
                }
            }

            writer.WriteLine();
        }
        private bool _IsApiKeyDirty(string jarPath, string aarPath, string apiKey)
        {
            bool isApiKeyDirty          = true;
            var  cachedCurrentDirectory = Directory.GetCurrentDirectory();
            var  tempDirectoryPath      = Path.Combine(cachedCurrentDirectory, FileUtil.GetUniqueTempPathInProject());

            if (!File.Exists(aarPath))
            {
                return(isApiKeyDirty);
            }
            try {
                // Move to a temp directory.
                Directory.CreateDirectory(tempDirectoryPath);
                Directory.SetCurrentDirectory(tempDirectoryPath);
                var tempAarPath = Path.Combine(tempDirectoryPath, "cloud_anchor_manifest.aar");
                File.Copy(aarPath, tempAarPath, true);
                // Extract the aar.
                string output;
                string errors;
                ShellHelper.RunCommand(jarPath, string.Format("xf \"{0}\"", tempAarPath),
                                       out output, out errors);
                // Read Api key parameter in manifest file.
                var         manifestPath = Path.Combine(tempDirectoryPath, "AndroidManifest.xml");
                XmlDocument xmlDocument  = new XmlDocument();
                xmlDocument.Load(manifestPath);
                XmlNode metaDataNode = xmlDocument.SelectSingleNode("/manifest/application/meta-data");
                string  oldApiKey    = metaDataNode.Attributes["android:value"].Value;
                isApiKeyDirty = !apiKey.Equals(oldApiKey);
            } finally {
                // Cleanup.
                Directory.SetCurrentDirectory(cachedCurrentDirectory);
                Directory.Delete(tempDirectoryPath, true);
            }
            return(isApiKeyDirty);
        }
        private static void _RunDirtyQualityJobs(AugmentedImageDatabase database)
        {
            if (database == null)
            {
                return;
            }
            if (s_DatabaseForQualityJobs != database)
            {
                // If another database is already running quality evaluation,
                // stop all pending jobs to prioritise the current database.
                if (s_DatabaseForQualityJobs != null)
                {
                    s_QualityBackgroundExecutor.RemoveAllPendingJobs();
                }
                s_DatabaseForQualityJobs = database;
            }
            _UpdateDatabaseQuality(database);
            // Set database dirty to refresh inspector UI for each frame that there are still pending jobs.
            // Otherwise if there exists one frame with no newly finished jobs, the UI will never get refreshed.
            // EditorUtility.SetDirty can only be called from main thread.
            if (s_QualityBackgroundExecutor.PendingJobsCount > 0)
            {
                EditorUtility.SetDirty(database);
                return;
            }
            List <AugmentedImageDatabaseEntry> dirtyEntries = database.GetDirtyQualityEntries();

            if (dirtyEntries.Count == 0)
            {
                return;
            }
            string cliBinaryPath;

            if (!AugmentedImageDatabase.FindCliBinaryPath(out cliBinaryPath))
            {
                return;
            }
            for (int i = 0; i < dirtyEntries.Count; ++i)
            {
                AugmentedImageDatabaseEntry image = dirtyEntries[i];
                var imagePath   = AssetDatabase.GetAssetPath(image.Texture);
                var textureGUID = image.TextureGUID;
                s_QualityBackgroundExecutor.PushJob(() => {
                    string quality;
                    string error;
                    ShellHelper.RunCommand(cliBinaryPath,
                                           string.Format("eval-img --input_image_path \"{0}\"", imagePath), out quality, out error);
                    if (!string.IsNullOrEmpty(error))
                    {
                        Debug.LogWarning(error);
                        return;
                    }
                    lock (s_UpdatedQualityScores) {
                        s_UpdatedQualityScores.Add(textureGUID, quality);
                    }
                });
            }
            // For refreshing inspector UI as new jobs have been enqueued.
            EditorUtility.SetDirty(database);
        }
        private static void WritePackageVersionString(
            string adbPath, string package, StreamWriter writer)
        {
            if (string.IsNullOrEmpty(adbPath))
            {
                writer.WriteLine("error: adb path was null");
            }
            else
            {
                string stdOut;
                string stdErr;
                string arguments = "shell pm dump " + package + " | " +
                                   "egrep -m 1 -i 'versionName' | sed -n 's/.*versionName=//p'";

                ShellHelper.RunCommand(adbPath, arguments, out stdOut, out stdErr);

                // If stdOut is populated, the device is connected and the app is installed
                if (!string.IsNullOrEmpty(stdOut))
                {
                    writer.WriteLine(stdOut);
                }
                else
                {
                    // If stdErr isn't empty, then either the device isn't connected or something
                    // else went wrong, such as adb not being installed.
                    if (!string.IsNullOrEmpty(stdErr))
                    {
                        writer.WriteLine(stdErr);
                    }
                    else
                    {
                        // If stdErr is empty, then the device is connected and the app isn't
                        // installed
                        writer.WriteLine(package + " is not installed on device");
                    }
                }
            }

            writer.WriteLine();
        }
Ejemplo n.º 6
0
        private void _PreprocessAndroidBuild()
        {
            // Get the Jdk path.
            var jdkPath = UnityEditor.EditorPrefs.GetString("JdkPath");

            if (string.IsNullOrEmpty(jdkPath))
            {
                Debug.Log("JDK path Unity pref is not set. Falling back to JAVA_HOME environment variable.");
                jdkPath = System.Environment.GetEnvironmentVariable("JAVA_HOME");
            }

            if (string.IsNullOrEmpty(jdkPath))
            {
                throw new BuildFailedException("A JDK path needs to be specified for the Android build.");
            }

            bool cloudAnchorsEnabled = !string.IsNullOrEmpty(ARCoreProjectSettings.Instance.CloudServicesApiKey);

            var cachedCurrentDirectory = Directory.GetCurrentDirectory();
            var pluginsFolderPath      = Path.Combine(cachedCurrentDirectory,
                                                      AssetDatabase.GUIDToAssetPath(k_PluginsFolderGuid));
            string cloudAnchorsManifestAarPath = Path.Combine(pluginsFolderPath, "cloud_anchor_manifest.aar");
            var    jarPath = Path.Combine(jdkPath, "bin/jar");

            if (cloudAnchorsEnabled)
            {
                // If the Api Key didn't change then do nothing.
                if (!_IsApiKeyDirty(jarPath, cloudAnchorsManifestAarPath,
                                    ARCoreProjectSettings.Instance.CloudServicesApiKey))
                {
                    return;
                }

                // Replace the project's cloud anchor AAR with the newly generated AAR.
                Debug.Log("Enabling Cloud Anchors in this build.");

                var tempDirectoryPath = Path.Combine(cachedCurrentDirectory, FileUtil.GetUniqueTempPathInProject());

                try
                {
                    // Move to a temp directory.
                    Directory.CreateDirectory(tempDirectoryPath);
                    Directory.SetCurrentDirectory(tempDirectoryPath);

                    var manifestTemplatePath = Path.Combine(cachedCurrentDirectory, AssetDatabase.GUIDToAssetPath(k_ManifestTemplateGuid));

                    // Extract the "template AAR" and remove it.
                    string output;
                    string errors;
                    ShellHelper.RunCommand(jarPath, string.Format("xf \"{0}\"", manifestTemplatePath),
                                           out output, out errors);

                    // Replace Api key template parameter in manifest file.
                    var manifestPath = Path.Combine(tempDirectoryPath, "AndroidManifest.xml");
                    var manifestText = File.ReadAllText(manifestPath);
                    manifestText = manifestText.Replace("{{CLOUD_ANCHOR_API_KEY}}", ARCoreProjectSettings.Instance.CloudServicesApiKey);
                    File.WriteAllText(manifestPath, manifestText);

                    // Compress the new AAR.
                    var fileListBuilder = new StringBuilder();
                    foreach (var filePath in Directory.GetFiles(tempDirectoryPath))
                    {
                        fileListBuilder.AppendFormat(" {0}", Path.GetFileName(filePath));
                    }

                    ShellHelper.RunCommand(jarPath,
                                           string.Format("cf cloud_anchor_manifest.aar {0}", fileListBuilder.ToString()),
                                           out output, out errors);

                    if (!string.IsNullOrEmpty(errors))
                    {
                        throw new BuildFailedException(
                                  string.Format("Error creating jar for cloud anchor manifest: {0}", errors));
                    }

                    File.Copy(Path.Combine(tempDirectoryPath, "cloud_anchor_manifest.aar"),
                              cloudAnchorsManifestAarPath, true);
                }
                finally
                {
                    // Cleanup.
                    Directory.SetCurrentDirectory(cachedCurrentDirectory);
                    Directory.Delete(tempDirectoryPath, true);

                    AssetDatabase.Refresh();
                }

                AssetHelper.GetPluginImporterByName("cloud_anchor_manifest.aar")
                .SetCompatibleWithPlatform(BuildTarget.Android, true);
            }
            else
            {
                Debug.Log("A cloud anchor API key has not been set.  Cloud anchors are disabled in this build.");
                File.Delete(cloudAnchorsManifestAarPath);
                AssetDatabase.Refresh();
            }
        }
        /// <summary>
        /// Tries to install and run the Instant Preview android app.
        /// </summary>
        /// <param name="adbPath">Path to adb to use for installing.</param>
        /// <param name="localVersion">Local version of Instant Preview plugin to compare installed
        /// APK against.</param>
        /// <returns>Enumerator for coroutine that handles installation if necessary.</returns>
        private static IEnumerator InstallApkAndRunIfConnected(string adbPath, string localVersion)
        {
            string apkPath = null;

#if UNITY_EDITOR
            apkPath = UnityEditor.AssetDatabase.GUIDToAssetPath(k_ApkGuid);
#endif // !UNITY_EDITOR

            // Early outs if set to install but the apk can't be found.
            if (!File.Exists(apkPath))
            {
                Debug.LogErrorFormat(
                    "Trying to install Instant Preview APK but reference to InstantPreview.apk " +
                    "is broken. Couldn't find an asset with .meta file guid={0}.", k_ApkGuid);
                yield break;
            }

            Result result = new Result();

            Thread checkAdbThread = new Thread((object obj) =>
            {
                Result res = (Result)obj;
                string output;
                string errors;

                // Gets version of installed apk.
                ShellHelper.RunCommand(adbPath,
                                       "shell dumpsys package com.google.ar.core.instantpreview | grep versionName",
                                       out output, out errors);
                string installedVersion = null;
                if (!string.IsNullOrEmpty(output) && string.IsNullOrEmpty(errors))
                {
                    installedVersion = output.Substring(output.IndexOf('=') + 1);
                }

                // Early outs if no device is connected.
                if (string.Compare(errors, k_NoDevicesFoundAdbResult) == 0)
                {
                    return;
                }

                // Prints errors and exits on failure.
                if (!string.IsNullOrEmpty(errors))
                {
                    Debug.LogError(errors);
                    return;
                }

                if (installedVersion == null)
                {
                    Debug.LogFormat(
                        "Instant Preview app not installed on device.",
                        apkPath);
                }
                else if (installedVersion != localVersion)
                {
                    Debug.LogFormat(
                        "Instant Preview installed version \"{0}\" does not match local version " +
                        "\"{1}\".",
                        installedVersion, localVersion);
                }

                res.ShouldPromptForInstall = installedVersion != localVersion;
            });
            checkAdbThread.Start(result);

            while (!checkAdbThread.Join(0))
            {
                yield return(0);
            }

            if (result.ShouldPromptForInstall)
            {
                if (PromptToInstall())
                {
                    Thread installThread = new Thread(() =>
                    {
                        string output;
                        string errors;

                        Debug.LogFormat(
                            "Installing Instant Preview app version {0}.",
                            localVersion);

                        ShellHelper.RunCommand(adbPath,
                                               "uninstall com.google.ar.core.instantpreview",
                                               out output, out errors);

                        ShellHelper.RunCommand(adbPath,
                                               string.Format("install \"{0}\"", apkPath),
                                               out output, out errors);

                        // Prints any output from trying to install.
                        if (!string.IsNullOrEmpty(output))
                        {
                            Debug.LogFormat("Instant Preview installation:\n{0}", output);
                        }

                        if (!string.IsNullOrEmpty(errors) && errors != "Success")
                        {
                            Debug.LogErrorFormat(
                                "Failed to install Instant Preview app:\n{0}", errors);
                        }
                    });
                    installThread.Start();

                    while (!installThread.Join(0))
                    {
                        yield return(0);
                    }
                }
                else
                {
                    yield break;
                }
            }
        }
        private static IEnumerator UpdateLoop(string adbPath)
        {
            var renderEventFunc     = NativeApi.GetRenderEventFunc();
            var shouldConvertToBgra =
                SystemInfo.graphicsDeviceType == GraphicsDeviceType.Direct3D11;
            var loggedAspectRatioWarning = false;

            // Waits until the end of the first frame until capturing the screen size,
            // because it might be incorrect when first querying it.
            yield return(k_WaitForEndOfFrame);

            var currentWidth        = 0;
            var currentHeight       = 0;
            var needToStartActivity = true;
            var prevFrameLandscape  = false;

            RenderTexture screenTexture = null;
            RenderTexture targetTexture = null;
            RenderTexture bgrTexture    = null;

            // Begins update loop. The coroutine will cease when the
            // ARCoreSession component it's called from is destroyed.
            for (;;)
            {
                yield return(k_WaitForEndOfFrame);

                var curFrameLandscape = Screen.width > Screen.height;
                if (prevFrameLandscape != curFrameLandscape)
                {
                    needToStartActivity = true;
                }

                prevFrameLandscape = curFrameLandscape;
                if (needToStartActivity)
                {
                    string activityName = curFrameLandscape ? "InstantPreviewLandscapeActivity" :
                                          "InstantPreviewActivity";
                    string output;
                    string errors;
                    ShellHelper.RunCommand(adbPath,
                                           "shell am start -S -n com.google.ar.core.instantpreview/." + activityName,
                                           out output, out errors);
                    needToStartActivity = false;
                }

                // Creates a target texture to capture the preview window onto.
                // Some video encoders prefer the dimensions to be a multiple of 16.
                var targetWidth  = RoundUpToNearestMultipleOf16(Screen.width);
                var targetHeight = RoundUpToNearestMultipleOf16(Screen.height);

                if (targetWidth != currentWidth || targetHeight != currentHeight)
                {
                    screenTexture = new RenderTexture(targetWidth, targetHeight, 0);
                    targetTexture = screenTexture;

                    if (shouldConvertToBgra)
                    {
                        bgrTexture = new RenderTexture(
                            screenTexture.width, screenTexture.height, 0,
                            RenderTextureFormat.BGRA32);
                        targetTexture = bgrTexture;
                    }

                    currentWidth  = targetWidth;
                    currentHeight = targetHeight;
                }

                NativeApi.Update();
                InstantPreviewInput.Update();

                if (NativeApi.AppShowedTouchWarning())
                {
                    Debug.LogWarning(k_InstantPreviewInputWarning);
                    NativeApi.UnityLoggedTouchWarning();
                }

                AddInstantPreviewTrackedPoseDriverWhenNeeded();

                Graphics.Blit(null, screenTexture);

                if (shouldConvertToBgra)
                {
                    Graphics.Blit(screenTexture, bgrTexture);
                }

                var cameraTexture = Frame.CameraImage.Texture;
                if (!loggedAspectRatioWarning && cameraTexture != null)
                {
                    var sourceWidth            = cameraTexture.width;
                    var sourceHeight           = cameraTexture.height;
                    var sourceAspectRatio      = (float)sourceWidth / sourceHeight;
                    var destinationWidth       = Screen.width;
                    var destinationHeight      = Screen.height;
                    var destinationAspectRatio = (float)destinationWidth / destinationHeight;

                    if (Mathf.Abs(sourceAspectRatio - destinationAspectRatio) >
                        k_MaxTolerableAspectRatioDifference)
                    {
                        Debug.LogWarningFormat(
                            k_MismatchedAspectRatioWarningFormatString, sourceAspectRatio,
                            destinationAspectRatio, sourceWidth, sourceHeight);
                        loggedAspectRatioWarning = true;
                    }
                }

                NativeApi.SendFrame(targetTexture.GetNativeTexturePtr());
                GL.IssuePluginEvent(renderEventFunc, (int)ApiRenderEvent.UpdateCubemapTexture);
            }
        }
Ejemplo n.º 9
0
        private void PreprocessAndroidBuild()
        {
            string cachedCurrentDirectory = Directory.GetCurrentDirectory();
            string pluginsFolderPath      = Path.Combine(cachedCurrentDirectory,
                                                         AssetDatabase.GUIDToAssetPath(_pluginsFolderGuid));
            string customizedManifestAarPath =
                Path.Combine(pluginsFolderPath, "customized_manifest.aar");

            string jarPath = Path.Combine(AndroidDependenciesHelper.GetJdkPath(), "bin/jar");

            var tempDirectoryPath =
                Path.Combine(cachedCurrentDirectory, FileUtil.GetUniqueTempPathInProject());

            try
            {
                // Move to a temp directory.
                Directory.CreateDirectory(tempDirectoryPath);
                Directory.SetCurrentDirectory(tempDirectoryPath);

                CreateClassesJar(jarPath, tempDirectoryPath);

                CheckCompatibilityWithAllSesssionConfigs(
                    ARCoreProjectSettings.Instance, GetAllSessionConfigs());
                XDocument customizedManifest =
                    GenerateCustomizedAndroidManifest(ARCoreProjectSettings.Instance);
                var manifestPath = Path.Combine(tempDirectoryPath, "AndroidManifest.xml");
                customizedManifest.Save(manifestPath);

                // Compress the new AAR.
                var fileListBuilder = new StringBuilder();
                foreach (var filePath in Directory.GetFiles(tempDirectoryPath))
                {
                    fileListBuilder.AppendFormat(" {0}", Path.GetFileName(filePath));
                }

                string command = string.Format(
                    "cf customized_manifest.aar {0}", fileListBuilder.ToString());

                string output;
                string errors;
                ShellHelper.RunCommand(jarPath, command, out output, out errors);

                if (!string.IsNullOrEmpty(errors))
                {
                    throw new BuildFailedException(
                              string.Format(
                                  "Error creating aar for manifest: {0}", errors));
                }

                File.Copy(Path.Combine(tempDirectoryPath, "customized_manifest.aar"),
                          customizedManifestAarPath, true);
            }
            finally
            {
                // Cleanup.
                Directory.SetCurrentDirectory(cachedCurrentDirectory);
                Directory.Delete(tempDirectoryPath, true);

                AssetDatabase.Refresh();
            }

            AssetHelper.GetPluginImporterByName("customized_manifest.aar")
            .SetCompatibleWithPlatform(BuildTarget.Android, true);
        }
        private void StripAndBackupClientAar()
        {
            // Strip the <queries> tag from the arcore_client.aar when it's incompatible with
            // Unity's built-in gradle version.
            string cachedCurrentDirectory = Directory.GetCurrentDirectory();
            string pluginsFolderPath      = Path.Combine(cachedCurrentDirectory,
                                                         AssetDatabase.GUIDToAssetPath(_pluginsFolderGuid));

            string[] plugins       = Directory.GetFiles(pluginsFolderPath);
            string   clientAarPath = string.Empty;

            foreach (var pluginPath in plugins)
            {
                if (pluginPath.Contains(_clientAarName) &&
                    !pluginPath.Contains(".meta") &&
                    AssetHelper.GetPluginImporterByName(Path.GetFileName(pluginPath))
                    .GetCompatibleWithPlatform(BuildTarget.Android))
                {
                    clientAarPath = pluginPath;
                    break;
                }
            }

            if (string.IsNullOrEmpty(clientAarPath))
            {
                throw new BuildFailedException(
                          string.Format("Cannot find a valid arcore client plugin under '{0}'",
                                        pluginsFolderPath));
            }

            string clientAarFileName = Path.GetFileName(clientAarPath);
            string jarPath           = AndroidDependenciesHelper.GetJdkPath();

            if (string.IsNullOrEmpty(jarPath))
            {
                throw new BuildFailedException("Cannot find a valid JDK path in this build.");
            }

            jarPath = Path.Combine(jarPath, "bin/jar");
            var tempDirectoryPath =
                Path.Combine(cachedCurrentDirectory, FileUtil.GetUniqueTempPathInProject());

            // Back up existing client AAR.
            string backupFilename = clientAarFileName + _backupExtension;

            Debug.LogFormat("Backing up {0} in {1}.", clientAarFileName, backupFilename);
            File.Copy(clientAarPath, Path.Combine(pluginsFolderPath, backupFilename), true);
            _hasBackup = true;

            Debug.LogFormat("Stripping the <queries> tag from {0} in this build.",
                            clientAarFileName);
            try
            {
                // Move to a temp directory.
                Directory.CreateDirectory(tempDirectoryPath);
                Directory.SetCurrentDirectory(tempDirectoryPath);

                // Extract the existing AAR in the temp directory.
                string output;
                string errors;
                ShellHelper.RunCommand(
                    jarPath, string.Format("xf \"{0}\"", clientAarPath), out output,
                    out errors);

                // Strip the <queries> tag from AndroidManifest.xml.
                var manifestPath = Path.Combine(tempDirectoryPath, "AndroidManifest.xml");
                var manifestText = File.ReadAllText(manifestPath);
                manifestText = System.Text.RegularExpressions.Regex.Replace(
                    manifestText, "(/s)?<queries>(.*)</queries>(\n|\r|\r\n)", string.Empty,
                    System.Text.RegularExpressions.RegexOptions.Singleline);
                File.WriteAllText(manifestPath, manifestText);

                // Compress the modified AAR.
                string command = string.Format("cf {0} .", clientAarFileName);
                ShellHelper.RunCommand(
                    jarPath,
                    command,
                    out output,
                    out errors);

                if (!string.IsNullOrEmpty(errors))
                {
                    throw new BuildFailedException(
                              string.Format(
                                  "Error creating jar for stripped arcore client manifest: {0}", errors));
                }

                // Override the existing client AAR with the modified one.
                File.Copy(Path.Combine(tempDirectoryPath, clientAarFileName),
                          clientAarPath, true);
            }
            finally
            {
                // Cleanup.
                Directory.SetCurrentDirectory(cachedCurrentDirectory);
                Directory.Delete(tempDirectoryPath, true);

                AssetDatabase.Refresh();
            }

            AssetHelper.GetPluginImporterByName(clientAarFileName)
            .SetCompatibleWithPlatform(BuildTarget.Android, true);
        }
        private static void CaptureBugReport()
        {
            string desktopPath = Environment.GetFolderPath(
                Environment.SpecialFolder.Desktop);
            DateTime timeStamp         = DateTime.Now;
            string   fileNameTimestamp = timeStamp.ToString("yyyyMMdd_hhmmss");
            string   filePath          = Path.Combine(
                desktopPath, k_FileNamePrefix + fileNameTimestamp + ".txt");
            StreamWriter writer;

            // Operating system and hardware info have to be handled separately based on OS
            switch (SystemInfo.operatingSystemFamily)
            {
            case OperatingSystemFamily.MacOSX:
                writer = File.CreateText(filePath);

                writer.WriteLine("*** GOOGLE ARCORE SDK FOR UNITY OSX BUG REPORT ***");
                writer.WriteLine("Timestamp: " + timeStamp.ToString());

                writer.WriteLine();
                writer.WriteLine("*** OPERATING SYSTEM INFORMATION ***");
                WriteCommand("system_profiler", "SPSoftwareDataType", writer);

                writer.WriteLine("*** GRAPHICS INFORMATION ***");
                WriteCommand("system_profiler", "SPDisplaysDataType", writer);

                WriteOsIndependentFields(writer);

                string stdOut;
                string stdErr;

                // Get PATH directories to search for adb in.
                ShellHelper.RunCommand(
                    "/bin/bash", "-c -l \"echo $PATH\"", out stdOut, out stdErr);
                stdOut.Trim();

                writer.WriteLine("*** ADB VERSIONS ON PATH ***");
                WriteAdbPathVersions(stdOut.Split(':'), writer);

                writer.WriteLine("*** TYPE -A ADB ***");
                WriteCommand("/bin/bash", "-c -l \"type -a adb\"", writer);

                writer.WriteLine("*** RUNNING ADB PROCESSES ***");
                WriteCommand(
                    "/bin/bash", "-c -l \"ps -ef | grep -i adb | grep -v grep\"", writer);

                writer.WriteLine("*** RUNNING UNITY PROCESSES ***");
                WriteCommand(
                    "/bin/bash", "-c -l \"ps -ef | grep -i Unity | grep -v grep\"", writer);

                writer.Close();

                Debug.Log(
                    "ARCore bug report captured. File can be found here:\n" +
                    Path.GetFullPath(filePath));
                break;

            case OperatingSystemFamily.Windows:
                writer = File.CreateText(filePath);

                writer.WriteLine("*** GOOGLE ARCORE SDK FOR UNITY WINDOWS BUG REPORT ***");
                writer.WriteLine("Timestamp: " + timeStamp.ToString());

                writer.WriteLine("*** OPERATING SYSTEM INFORMATION ***");
                WriteCommand("cmd.exe", "/C systeminfo", writer);

                writer.WriteLine("*** GRAPHICS INFORMATION ***");
                WriteCommand(
                    "cmd.exe", "/C wmic path win32_VideoController get /format:list", writer);

                WriteOsIndependentFields(writer);

                string pathStr = Environment.GetEnvironmentVariable("PATH").Trim();

                writer.WriteLine("*** ADB VERSIONS ON PATH ***");
                WriteAdbPathVersions(pathStr.Split(';'), writer);

                writer.WriteLine("*** RUNNING ADB PROCESSES ***");
                WriteCommand("cmd.exe",
                             "/C TASKLIST | c:\\Windows\\System32\\findstr.exe \"adb\"", writer);

                writer.WriteLine("*** RUNNING UNITY PROCESSES ***");
                WriteCommand("cmd.exe",
                             "/C TASKLIST | c:\\Windows\\System32\\findstr.exe \"Unity\"", writer);

                writer.Close();

                Debug.Log(
                    "ARCore bug report captured. File can be found here:\n" +
                    Path.GetFullPath(filePath));
                break;

            default:
                string dialogMessage = "ARCore does not support capturing bug reports for " +
                                       SystemInfo.operatingSystemFamily + " at this time.";

                EditorUtility.DisplayDialog("ARCore Bug Report", dialogMessage, "OK");
                break;
            }
        }
        private void SetApiKeyOnAndroid()
        {
            string cachedCurrentDirectory = Directory.GetCurrentDirectory();
            string pluginsFolderPath      = Path.Combine(cachedCurrentDirectory,
                                                         AssetDatabase.GUIDToAssetPath(_pluginsFolderGuid));
            string cloudAnchorsManifestAarPath = Path.Combine(pluginsFolderPath, _aarFileName);

            bool cloudAnchorsEnabled =
                !string.IsNullOrEmpty(ARCoreProjectSettings.Instance.CloudServicesApiKey);

            if (cloudAnchorsEnabled)
            {
                string jarPath = AndroidDependenciesHelper.GetJdkPath();
                if (string.IsNullOrEmpty(jarPath))
                {
                    throw new BuildFailedException("Cannot find a valid JDK path in this build.");
                }

                jarPath = Path.Combine(jarPath, "bin/jar");

                // Replace the project's cloud anchor AAR with the newly generated AAR.
                Debug.Log("Enabling Cloud Anchors with API Key Authentication in this build.");

                var tempDirectoryPath =
                    Path.Combine(cachedCurrentDirectory, FileUtil.GetUniqueTempPathInProject());

                try
                {
                    // Move to a temp directory.
                    Directory.CreateDirectory(tempDirectoryPath);
                    Directory.SetCurrentDirectory(tempDirectoryPath);

                    var manifestTemplatePath = Path.Combine(
                        cachedCurrentDirectory,
                        AssetDatabase.GUIDToAssetPath(_manifestTemplateGuid));

                    // Extract the "template AAR" and remove it.
                    string output;
                    string errors;
                    ShellHelper.RunCommand(
                        jarPath, string.Format("xf \"{0}\"", manifestTemplatePath), out output,
                        out errors);

                    // Replace Api key template parameter in manifest file.
                    var manifestPath = Path.Combine(tempDirectoryPath, "AndroidManifest.xml");
                    var manifestText = File.ReadAllText(manifestPath);
                    manifestText = manifestText.Replace(
                        "{{CLOUD_ANCHOR_API_KEY}}",
                        ARCoreProjectSettings.Instance.CloudServicesApiKey);
                    File.WriteAllText(manifestPath, manifestText);

                    // Compress the new AAR.
                    var fileListBuilder = new StringBuilder();
                    foreach (var filePath in Directory.GetFiles(tempDirectoryPath))
                    {
                        fileListBuilder.AppendFormat(" {0}", Path.GetFileName(filePath));
                    }

                    string command = string.Format(
                        "cf {0} {1}", _aarFileName, fileListBuilder.ToString());

                    ShellHelper.RunCommand(
                        jarPath,
                        command,
                        out output,
                        out errors);

                    if (!string.IsNullOrEmpty(errors))
                    {
                        throw new BuildFailedException(
                                  string.Format(
                                      "Error creating jar for cloud anchor manifest: {0}", errors));
                    }

                    File.Copy(Path.Combine(tempDirectoryPath, _aarFileName),
                              cloudAnchorsManifestAarPath, true);
                }
                finally
                {
                    // Cleanup.
                    Directory.SetCurrentDirectory(cachedCurrentDirectory);
                    Directory.Delete(tempDirectoryPath, true);

                    AssetDatabase.Refresh();
                }

                AssetHelper.GetPluginImporterByName(_aarFileName)
                .SetCompatibleWithPlatform(BuildTarget.Android, true);
            }
            else
            {
                Debug.Log(
                    "Cloud Anchor API key has not been set in this build.");
                File.Delete(cloudAnchorsManifestAarPath);
                AssetDatabase.Refresh();
            }
        }