using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor.Android;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using Unity.XR.Oculus;
using UnityEditor;
using UnityEditor.XR.Management;
using UnityEngine.XR.Management;

namespace UnityEditor.XR.Oculus
{
    public class OculusBuildProcessor : XRBuildHelper<OculusSettings>
    {
        public override string BuildSettingsKey { get {return "Unity.XR.Oculus.Settings";} }

        private static List<BuildTarget> s_ValidStandaloneBuildTargets = new List<BuildTarget>()
        {
            BuildTarget.StandaloneWindows64
        };

        private bool IsCurrentBuildTargetVaild(BuildReport report)
        {
            return report.summary.platformGroup == BuildTargetGroup.Android ||
                (report.summary.platformGroup == BuildTargetGroup.Standalone && s_ValidStandaloneBuildTargets.Contains(report.summary.platform));
        }

        private bool HasLoaderEnabledForTarget(BuildTargetGroup buildTargetGroup)
        {
            if (buildTargetGroup != BuildTargetGroup.Standalone && buildTargetGroup != BuildTargetGroup.Android)
                return false;

            XRGeneralSettings settings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(buildTargetGroup);
            if (settings == null)
                return false;

            bool loaderFound = false;
            for (int i = 0; i < settings.Manager.activeLoaders.Count; ++i)
            {
                if (settings.Manager.activeLoaders[i] as OculusLoader != null)
                {
                    loaderFound = true;
                    break;
                }
            }

            return loaderFound;
        }

        private readonly string spatializerPluginName = "AudioPluginOculusSpatializer";
        private readonly string spatializerReadableName = "OculusSpatializer";

        private readonly string[] runtimePluginNames = new string[]
        {
            "OculusXRPlugin.dll",
            "OVRPlugin.dll",
            "libOculusXRPlugin.so",
            "OVRPlugin.aar"
        };

        private bool ShouldIncludeRuntimePluginsInBuild(string path, BuildTargetGroup platformGroup)
        {
            return HasLoaderEnabledForTarget(platformGroup);
        }

        private bool ShouldIncludeSpatializerPluginsInBuild(string path)
        {
            string currentSpatializerPluginName = AudioSettings.GetSpatializerPluginName();

            if (string.Compare(spatializerReadableName, currentSpatializerPluginName, true) == 0)
                return true;

            return false;
        }

        /// <summary>OnPreprocessBuild override to provide XR Plugin specific build actions.</summary>
        /// <param name="report">The build report.</param>
        public override void OnPreprocessBuild(BuildReport report)
        {
            if (IsCurrentBuildTargetVaild(report) && HasLoaderEnabledForTarget(report.summary.platformGroup))
                base.OnPreprocessBuild(report);

            var allPlugins = PluginImporter.GetAllImporters();
            foreach (var plugin in allPlugins)
            {
                if (plugin.isNativePlugin)
                {
                    foreach (var pluginName in runtimePluginNames)
                    {
                        if (plugin.assetPath.Contains(pluginName))
                        {
                            plugin.SetIncludeInBuildDelegate((path) => { return ShouldIncludeRuntimePluginsInBuild(path, report.summary.platformGroup); });
                            break;
                        }
                    }

                    // exlude spatializer related plugins if OculusSpatializer not selected under Audio setting
                    if (plugin.assetPath.Contains(spatializerPluginName))
                    {
                        plugin.SetIncludeInBuildDelegate(ShouldIncludeSpatializerPluginsInBuild);
                    }
                }
            }
        }
    }

    public static class OculusBuildTools
    {
        public static bool OculusLoaderPresentInSettingsForBuildTarget(BuildTargetGroup btg)
        {
            var generalSettingsForBuildTarget = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(btg);
            if (!generalSettingsForBuildTarget)
                return false;
            var settings = generalSettingsForBuildTarget.AssignedSettings;
            if (!settings)
                return false;

            bool loaderFound = false;
            for (int i = 0; i < settings.activeLoaders.Count; ++i)
            {
                if (settings.activeLoaders[i] as OculusLoader != null)
                {
                    loaderFound = true;
                    break;
                }
            }

            return loaderFound;
        }

        public static OculusSettings GetSettings()
        {
            OculusSettings settings = null;
#if UNITY_EDITOR
            UnityEditor.EditorBuildSettings.TryGetConfigObject<OculusSettings>("Unity.XR.Oculus.Settings", out settings);
#else
            settings = OculusSettings.s_Settings;
#endif
            return settings;
        }
    }

    [InitializeOnLoad]
    public static class OculusEnterPlayModeSettingsCheck
    {
        static OculusEnterPlayModeSettingsCheck()
        {
            EditorApplication.playModeStateChanged += PlaymodeStateChangedEvent;
        }

        private static void PlaymodeStateChangedEvent(PlayModeStateChange state)
        {
            if (state == PlayModeStateChange.EnteredPlayMode)
            {
                if (!OculusBuildTools.OculusLoaderPresentInSettingsForBuildTarget(BuildTargetGroup.Standalone))
                {
                    return;
                }

                if (PlayerSettings.GetGraphicsAPIs(BuildTarget.StandaloneWindows)[0] !=
                    GraphicsDeviceType.Direct3D11)
                {
                    Debug.LogError("D3D11 is currently the only graphics API compatible with the Oculus XR Plugin on desktop platforms. Please change the preferred Graphics API setting in Player Settings.");
                }
            }
        }
    }

    internal class OculusBuildHooks : IPreprocessBuildWithReport, IPostprocessBuildWithReport
    {
        public int callbackOrder { get; }

        internal static string s_AndroidManifestPath = "";

        private static readonly Dictionary<string, string> AndroidBootConfigVars = new Dictionary<string, string>()
        {
            { "xr-meta-enabled", "1" }
        };

        public void OnPreprocessBuild(BuildReport report)
        {
            if(!OculusBuildTools.OculusLoaderPresentInSettingsForBuildTarget(report.summary.platformGroup))
                return;

            var settings = OculusBuildTools.GetSettings();
            
            if (report.summary.platformGroup == BuildTargetGroup.Android)
            {
                GraphicsDeviceType firstGfxType = PlayerSettings.GetGraphicsAPIs(report.summary.platform)[0];

#if UNITY_2023_1_OR_NEWER
                if (firstGfxType != GraphicsDeviceType.OpenGLES3 && firstGfxType != GraphicsDeviceType.Vulkan)
                {
                    throw new BuildFailedException("OpenGLES3 and Vulkan are currently the only graphics APIs compatible with the Oculus XR Plugin on mobile platforms.");
                }
                
                if (PlayerSettings.colorSpace != ColorSpace.Linear && (firstGfxType == GraphicsDeviceType.OpenGLES3))
                {
                    throw new BuildFailedException("Only Linear Color Space is supported when using OpenGLES. Please set Color Space to Linear in Player Settings, or switch to Vulkan.");
                }
#else
                if (firstGfxType != GraphicsDeviceType.OpenGLES3 && firstGfxType != GraphicsDeviceType.Vulkan && firstGfxType != GraphicsDeviceType.OpenGLES2)
                {
                    throw new BuildFailedException("OpenGLES2, OpenGLES3, and Vulkan are currently the only graphics APIs compatible with the Oculus XR Plugin on mobile platforms.");
                }
                
                if (PlayerSettings.colorSpace != ColorSpace.Linear && (firstGfxType == GraphicsDeviceType.OpenGLES3 || firstGfxType == GraphicsDeviceType.OpenGLES2))
                {
                    throw new BuildFailedException("Only Linear Color Space is supported when using OpenGLES. Please set Color Space to Linear in Player Settings, or switch to Vulkan.");
                }
#endif

                if (PlayerSettings.Android.minSdkVersion < AndroidSdkVersions.AndroidApiLevel23)
                {
                    throw new BuildFailedException("Android Minimum API Level must be set to 23 or higher for the Oculus XR Plugin.");
                }

                // some features don't work in non-ARM64 builds
                if ((PlayerSettings.Android.targetArchitectures & AndroidArchitecture.ARM64) != AndroidArchitecture.ARM64)
                {
                    // ETFR requires ARM64
                    if ((settings != null) && (settings.FoveatedRenderingMethod == OculusSettings.FoveationMethod.EyeTrackedFoveatedRendering))
                    {
                        throw new BuildFailedException("Eye Tracked Foveated Rendering can only be enabled when ARM64 is selected as the Target Architecture.");
                    }
                }

                // write Android Meta tags to bootconfig
                var bootConfig = new BootConfig(report);
                bootConfig.ReadBootConfig();

                foreach (var entry in AndroidBootConfigVars)
                {
                    bootConfig.SetValueForKey(entry.Key, entry.Value);
                }

                bootConfig.WriteBootConfig();
            }

            if (report.summary.platform == BuildTarget.StandaloneWindows)
            {
                throw new BuildFailedException("The Oculus XR Plugin doesn't support 32-bit Windows player builds. Please use 64-bit instead.");
            }

            if (report.summary.platform == BuildTarget.StandaloneWindows || report.summary.platform == BuildTarget.StandaloneWindows64)
            {
                if (PlayerSettings.GetGraphicsAPIs(report.summary.platform)[0] !=
                    GraphicsDeviceType.Direct3D11)
                {
                    throw new BuildFailedException("D3D11 is currently the only graphics API compatible with the Oculus XR Plugin on desktop platforms. Please change the Graphics API setting in Player Settings.");
                }
            }
        }

        public void OnPostprocessBuild(BuildReport report)
        {
            if(!OculusBuildTools.OculusLoaderPresentInSettingsForBuildTarget(report.summary.platformGroup))
                return;
            
            if (report.summary.platformGroup == BuildTargetGroup.Android)
            {
                // clean up Android Meta boot settings after build
                BootConfig bootConfig = new BootConfig(report);
                bootConfig.ReadBootConfig();

                foreach (KeyValuePair<string, string> entry in AndroidBootConfigVars)
                {
                    bootConfig.ClearEntryForKeyAndValue(entry.Key, entry.Value);
                }

                bootConfig.WriteBootConfig();

                // clean up Android manifest after build
                if (!string.IsNullOrEmpty(s_AndroidManifestPath))
                {
                    try
                    {
                        File.Delete(s_AndroidManifestPath);
                    }
                    catch (Exception e)
                    {
                        // this only fails if the file can't be deleted; it is quiet if the file does not exist
                        Debug.LogWarning("Failed to clean up AndroidManifest.xml file located at " + s_AndroidManifestPath + " : " + e.ToString());
                    }
                }

                s_AndroidManifestPath = "";

                // verify settings
                var settings = OculusBuildTools.GetSettings();
                if (settings != null)
                {
                    GraphicsDeviceType firstGfxType = PlayerSettings.GetGraphicsAPIs(report.summary.platform)[0];
                    
                    if (settings.SymmetricProjection && (!(settings.TargetQuest2 || settings.TargetQuestPro) || settings.m_StereoRenderingModeAndroid != OculusSettings.StereoRenderingModeAndroid.Multiview || firstGfxType != GraphicsDeviceType.Vulkan))
                    {
                        Debug.LogWarning("Symmetric Projection is only supported on Quest 2 and Quest Pro with Vulkan and Multiview.");
                    }
                    
                    if (settings.SubsampledLayout && (!(settings.TargetQuest2 || settings.TargetQuestPro) || firstGfxType != GraphicsDeviceType.Vulkan))
                    {
                        Debug.LogWarning("Subsampled Layout is only supported on Quest 2 and Quest Pro with Vulkan.");
                    }

                    if (settings.DepthSubmission && (settings.m_StereoRenderingModeAndroid != OculusSettings.StereoRenderingModeAndroid.Multiview || firstGfxType != GraphicsDeviceType.Vulkan))
                    {
                        Debug.LogWarning("Depth Submission is only supported on Vulkan with Multiview.");
                    }

                    if (settings.DepthSubmission)
                    {
                        Debug.LogWarning("Enabling Depth Submission may cause a crash on application startup if MSAA is not enabled. This will be resolved in future versions of Unity.");
                    }
                }
            }

            if (EditorUserBuildSettings.waitForManagedDebugger && report.summary.platformGroup == BuildTargetGroup.Android && ((report.summary.options & BuildOptions.AutoRunPlayer) != 0))
                Debug.Log("[Wait For Managed Debugger To Attach] Use volume Up or Down button on headset to confirm ...\n");
        }
    }

    /// <summary>
    /// Small utility class for reading, updating and writing boot config.
    /// </summary>
    internal class BootConfig
    {
        private const string XrBootSettingsKey = "xr-boot-settings";

        private readonly Dictionary<string, string> bootConfigSettings;
        private readonly string buildTargetName;

        public BootConfig(BuildReport report)
        {
            bootConfigSettings = new Dictionary<string, string>();
            buildTargetName = BuildPipeline.GetBuildTargetName(report.summary.platform);
        }

        public void ReadBootConfig()
        {
            bootConfigSettings.Clear();

            string xrBootSettings = EditorUserBuildSettings.GetPlatformSettings(buildTargetName, XrBootSettingsKey);
            if (!string.IsNullOrEmpty(xrBootSettings))
            {
                // boot settings string format
                // <boot setting>:<value>[;<boot setting>:<value>]*
                var bootSettings = xrBootSettings.Split(';');
                foreach (var bootSetting in bootSettings)
                {
                    var setting = bootSetting.Split(':');
                    if (setting.Length == 2 && !string.IsNullOrEmpty(setting[0]) && !string.IsNullOrEmpty(setting[1]))
                    {
                        bootConfigSettings.Add(setting[0], setting[1]);
                    }
                }
            }
        }

        public void SetValueForKey(string key, string value) => bootConfigSettings[key] = value;

        public bool TryGetValue(string key, out string value) => bootConfigSettings.TryGetValue(key, out value);

        public void ClearEntryForKeyAndValue(string key, string value)
        {
            if (bootConfigSettings.TryGetValue(key, out string dictValue) && dictValue == value)
            {
                bootConfigSettings.Remove(key);
            }
        }

        public void WriteBootConfig()
        {
            // boot settings string format
            // <boot setting>:<value>[;<boot setting>:<value>]*
            bool firstEntry = true;
            var sb = new System.Text.StringBuilder();
            foreach (var kvp in bootConfigSettings)
            {
                if (!firstEntry)
                {
                    sb.Append(";");
                }

                sb.Append($"{kvp.Key}:{kvp.Value}");
                firstEntry = false;
            }

            EditorUserBuildSettings.SetPlatformSettings(buildTargetName, XrBootSettingsKey, sb.ToString());
        }
    }

#if UNITY_ANDROID
    internal class OculusManifest : IPostGenerateGradleAndroidProject
    {
        static readonly string k_AndroidURI = "http://schemas.android.com/apk/res/android";
        static readonly string k_AndroidManifestPath = "/src/main/AndroidManifest.xml";
        static readonly string k_AndroidProGuardPath = "/proguard-unity.txt";
        static readonly string k_OculusProGuardRule = Environment.NewLine + "-keep class com.unity.oculus.OculusUnity { *; }" + Environment.NewLine;

        void UpdateOrCreateAttributeInTag(XmlDocument doc, string parentPath, string tag, string name, string value)
        {
            var xmlNode = doc.SelectSingleNode(parentPath + "/" + tag);

            if (xmlNode != null)
            {
                ((XmlElement)xmlNode).SetAttribute(name, k_AndroidURI, value);
            }
        }

        void UpdateOrCreateNameValueElementsInTag(XmlDocument doc, string parentPath, string tag,
            string firstName, string firstValue, string secondName, string secondValue)
        {
            var xmlNodeList = doc.SelectNodes(parentPath + "/" + tag);

            foreach (XmlNode node in xmlNodeList)
            {
                var attributeList = ((XmlElement)node).Attributes;

                foreach (XmlAttribute attrib in attributeList)
                {
                    if (attrib.Value == firstValue)
                    {
                        XmlAttribute valueAttrib = attributeList[secondName, k_AndroidURI];
                        if (valueAttrib != null)
                        {
                            valueAttrib.Value = secondValue;
                        }
                        else
                        {
                            ((XmlElement)node).SetAttribute(secondName, k_AndroidURI, secondValue);
                        }
                        return;
                    }
                }
            }

            // Didn't find any attributes that matched, create both (or all three)
            XmlElement childElement = doc.CreateElement(tag);
            childElement.SetAttribute(firstName, k_AndroidURI, firstValue);
            childElement.SetAttribute(secondName, k_AndroidURI, secondValue);

            var xmlParentNode = doc.SelectSingleNode(parentPath);

            if (xmlParentNode != null)
            {
                xmlParentNode.AppendChild(childElement);
            }
        }

        // same as above, but don't create if the node already exists
        void CreateNameValueElementsInTag(XmlDocument doc, string parentPath, string tag,
            string firstName, string firstValue, string secondName=null, string secondValue=null, string thirdName=null, string thirdValue=null)
        {
            var xmlNodeList = doc.SelectNodes(parentPath + "/" + tag);

            // don't create if the firstValue matches
            foreach (XmlNode node in xmlNodeList)
            {
                foreach (XmlAttribute attrib in node.Attributes)
                {
                    if (attrib.Value == firstValue)
                    {
                        return;
                    }
                }
            }

            XmlElement childElement = doc.CreateElement(tag);
            childElement.SetAttribute(firstName, k_AndroidURI, firstValue);

            if (secondValue != null)
            {
                childElement.SetAttribute(secondName, k_AndroidURI, secondValue);
            }

            if (thirdValue != null)
            {
                childElement.SetAttribute(thirdName, k_AndroidURI, thirdValue);
            }

            var xmlParentNode = doc.SelectSingleNode(parentPath);

            if (xmlParentNode != null)
            {
                xmlParentNode.AppendChild(childElement);
            }
        }

        void RemoveNameValueElementInTag(XmlDocument doc, string parentPath, string tag, string name, string value)
        {
            var xmlNodeList = doc.SelectNodes(parentPath + "/" + tag);

            foreach (XmlNode node in xmlNodeList)
            {
                var attributeList = ((XmlElement)node).Attributes;

                foreach (XmlAttribute attrib in attributeList)
                {
                    if (attrib.Name == name && attrib.Value == value)
                    {
                        node.ParentNode?.RemoveChild(node);
                    }
                }
            }
        }

        // disable ProGuard on Oculus Java files
        void AddProGuardRule(string path)
        {
            try
            {
                var proguardPath = path + k_AndroidProGuardPath;

                if (File.Exists(proguardPath))
                {
                    File.AppendAllText(proguardPath, k_OculusProGuardRule);
                }
            }
            catch (Exception e)
            {
                Debug.LogWarning("Failed to append Oculus rule to ProGuard file: " + e.ToString());
            }
        }

        public void OnPostGenerateGradleAndroidProject(string path)
        {
            if(!OculusBuildTools.OculusLoaderPresentInSettingsForBuildTarget(BuildTargetGroup.Android))
                return;

            AddProGuardRule(path);

            var manifestPath = path + k_AndroidManifestPath;
            var manifestDoc = new XmlDocument();
            manifestDoc.Load(manifestPath);

            var sdkVersion = (int)PlayerSettings.Android.minSdkVersion;

            UpdateOrCreateAttributeInTag(manifestDoc, "/", "manifest", "installLocation", "auto");

            var nodePath = "/manifest/application";
            UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.samsung.android.vr.application.mode", "value", "vr_only");

            var settings = OculusBuildTools.GetSettings();

            var lowOverheadModeVal = ((settings != null) && settings.LowOverheadMode) ? "true" : "false";
            UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.unity.xr.oculus.LowOverheadMode", "value", lowOverheadModeVal);

            var lateLatchingVal = ((settings != null) && settings.LateLatching) ? "true" : "false";
            UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.unity.xr.oculus.LateLatching", "value", lateLatchingVal);

            var lateLatchingDebugVal = ((settings != null) && settings.LateLatchingDebug) ? "true" : "false";
            UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.unity.xr.oculus.LateLatchingDebug", "value", lateLatchingDebugVal);

            nodePath = "/manifest/application";
            UpdateOrCreateAttributeInTag(manifestDoc, nodePath, "activity", "screenOrientation", "landscape");
            UpdateOrCreateAttributeInTag(manifestDoc, nodePath, "activity", "theme", "@android:style/Theme.Black.NoTitleBar.Fullscreen");

            var configChangesValue = "keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|uiMode";
            configChangesValue = ((sdkVersion >= 24) ? configChangesValue + "|density" : configChangesValue);
            UpdateOrCreateAttributeInTag(manifestDoc, nodePath, "activity", "configChanges", configChangesValue);

            if (sdkVersion >= 24)
            {
                UpdateOrCreateAttributeInTag(manifestDoc, nodePath, "activity", "resizeableActivity", "false");
            }

            UpdateOrCreateAttributeInTag(manifestDoc, nodePath, "activity", "launchMode", "singleTask");

            nodePath = "/manifest";
            CreateNameValueElementsInTag(manifestDoc, nodePath, "uses-feature", "name", "android.hardware.vr.headtracking", "required", "true", "version", "1");

            var eyeTrackedFoveatedRendering = ((settings != null) && (settings.FoveatedRenderingMethod == OculusSettings.FoveationMethod.EyeTrackedFoveatedRendering));
            if (eyeTrackedFoveatedRendering)
            {
                CreateNameValueElementsInTag(manifestDoc, nodePath, "uses-feature", "name", "oculus.software.eye_tracking", "required", "false");
                CreateNameValueElementsInTag(manifestDoc, nodePath, "uses-permission", "name", "com.oculus.permission.EYE_TRACKING");
            }

            string supportedDevices = null;

            if (settings != null)
            {
                var deviceList = new List<string>();

                if (settings.TargetQuest2)
                    deviceList.Add("quest2");

                if (settings.TargetQuestPro)
                    deviceList.Add("cambria");

                if (deviceList.Count > 0)
                {
                    StringBuilder sb = new StringBuilder();
                    string delim = "";

                    foreach (string device in deviceList)
                    {
                        sb.Append(delim);
                        sb.Append(device);
                        delim = "|";
                    }

                    supportedDevices = sb.ToString();
                }
                else
                {
                    Debug.LogWarning("No target devices selected in Oculus Android project settings. No devices will be listed as supported in the application Android manifest.");
                }
            }
            else
            {
                supportedDevices = "quest2";
            }

            if (supportedDevices != null)
            {
                nodePath = "/manifest/application";
                UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.oculus.supportedDevices", "value", supportedDevices);
            }

            if (settings != null && settings.SystemSplashScreen != null)
            {
                string splashScreenAssetPath = AssetDatabase.GetAssetPath(settings.SystemSplashScreen);
                if (Path.GetExtension(splashScreenAssetPath).ToLower() != ".png")
                {
                    throw new BuildFailedException("Invalid file format of System Splash Screen. It has to be a PNG file to be used by the Quest OS. The asset path: " + splashScreenAssetPath);
                }
                else
                {
                    string sourcePath = splashScreenAssetPath;
                    string targetFolder = Path.Combine(path, "src/main/assets");
                    string targetPath = targetFolder + "/vr_splash.png";

                    // copy the splash over into the gradle folder and make sure it's not read only
                    FileUtil.ReplaceFile(sourcePath, targetPath);
                    FileInfo targetInfo = new FileInfo(targetPath);
                    targetInfo.IsReadOnly = false;
                }

                nodePath = "/manifest/application";
                UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.oculus.ossplash", "value", "true");
            }

            nodePath = "/manifest/application/activity";
            UpdateOrCreateNameValueElementsInTag(manifestDoc, nodePath, "meta-data", "name", "com.oculus.vr.focusaware", "value", "true");

            nodePath = "/manifest/application/activity/intent-filter";
            CreateNameValueElementsInTag(manifestDoc, nodePath, "category", "name", "com.oculus.intent.category.VR");

            // if the Microphone class is used in a project, the BLUETOOTH permission is automatically added to the manifest
            // we remove it here since it will cause projects to fail Oculus cert
            // this shouldn't affect Bluetooth HID devices, which don't need the permission
            nodePath = "/manifest";
            RemoveNameValueElementInTag(manifestDoc, nodePath, "uses-permission", "android:name", "android.permission.BLUETOOTH");

            manifestDoc.Save(manifestPath);

            // let OnPostprocessBuild() know which intermediate manifest to delete
            OculusBuildHooks.s_AndroidManifestPath = manifestPath;
        }

        public int callbackOrder { get { return 10000; } }

        void DebugPrint(XmlDocument doc)
        {
            var sw = new System.IO.StringWriter();
            var xw = XmlWriter.Create(sw);
            doc.Save(xw);
            Debug.Log(sw);
        }
    }
#endif
}