/******************************************************************************* Copyright © 2015-2022 PICO Technology Co., Ltd.All rights reserved. NOTICE:All information contained herein is, and remains the property of PICO Technology Co., Ltd. The intellectual and technical concepts contained herein are proprietary to PICO Technology Co., Ltd. and may be covered by patents, patents in process, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from PICO Technology Co., Ltd. *******************************************************************************/ using System; using System.Collections.Generic; using System.Xml; using UnityEngine; using UnityEngine.Rendering; using UnityEditor.Android; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor; using UnityEditor.XR.Management; using UnityEngine.XR.Management; #if UNITY_OPENXR #if XR_HAND using UnityEngine.XR.Hands.OpenXR; #endif #if PICO_OPENXR_SDK //using UnityEngine.XR.Hands.OpenXR; using Unity.XR.OpenXR.Features.PICOSupport; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; #endif #endif namespace Unity.XR.PXR.Editor { public class PXR_BuildProcessor : XRBuildHelper { public override string BuildSettingsKey { get { return "Unity.XR.PXR.Settings"; } } public static bool IsLoaderExists(bool isPlatform = false) { XRGeneralSettings generalSettings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Android); if (generalSettings == null) return false; var assignedSettings = generalSettings.AssignedSettings; if (assignedSettings == null) return false; bool isPxrOpenXRFeatureEnabled = false; bool isPxrOpenXRExtensionsEnabled = false; #if UNITY_2021_1_OR_NEWER foreach (XRLoader loader in assignedSettings.activeLoaders) { if (loader is PXR_Loader) return true; #if PICO_OPENXR_SDK if (loader is OpenXRLoader) { var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android); foreach (var feature in settings.GetFeatures()) { if (feature is PICOFeature) { isPxrOpenXRFeatureEnabled = feature.enabled; if (!isPlatform) { return isPxrOpenXRFeatureEnabled; } } if (feature is OpenXRExtensions) { isPxrOpenXRExtensionsEnabled = feature.enabled; if (isPlatform) { return isPxrOpenXRExtensionsEnabled; } } } } #endif } #else foreach (XRLoader loader in assignedSettings.loaders) { if (loader is PXR_Loader) return true; #if PICO_OPENXR_SDK if (loader is OpenXRLoader) { Debug.Log("PXRLog [Build Check]OpenXR is enabled"); var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android); foreach (var feature in settings.GetFeatures()) { if (feature is PXR_OpenXRFeature) { return true; } if (feature is PXR_OpenXRFeature) { isPxrOpenXRFeatureEnabled = feature.enabled; if (!isPlatform) { return isPxrOpenXRFeatureEnabled; } } if (feature is PXR_OpenXRExtensions) { isPxrOpenXRExtensionsEnabled = feature.enabled; if (isPlatform) { return isPxrOpenXRExtensionsEnabled; } } } } #endif } #endif return false; } private PXR_Settings PxrSettings { get { return SettingsForBuildTargetGroup(BuildTargetGroup.Android) as PXR_Settings; } } private void SetRequiredPluginInBuild() { PluginImporter[] plugins = PluginImporter.GetAllImporters(); foreach (PluginImporter plugin in plugins) { if (plugin.assetPath.Contains("PxrPlatform.aar")) { plugin.SetIncludeInBuildDelegate((path) => { return IsLoaderExists(true); }); } if (plugin.assetPath.Contains(".ForUnitySDK.aar")) { Debug.Log("PXRLog [Build Check]OpenXR is enabled"); plugin.SetIncludeInBuildDelegate((path) => { #if PICO_OPENXR_SDK var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android); foreach (var feature in settings.GetFeatures()) { if (feature is PICOFeature) { return feature.enabled; } } return false; #else return IsLoaderExists(true); #endif }); } } } public override void OnPreprocessBuild(BuildReport report) { SetRequiredPluginInBuild(); if (report.summary.platformGroup != BuildTargetGroup.Android) return; if (!IsLoaderExists()) return; GraphicsDeviceType firstGfxType = PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget)[0]; if (firstGfxType != GraphicsDeviceType.OpenGLES3 && firstGfxType != GraphicsDeviceType.Vulkan && firstGfxType != GraphicsDeviceType.OpenGLES2) throw new BuildFailedException($"PICO Plugin on mobile platforms nonsupport the {firstGfxType}"); if (PxrSettings.stereoRenderingModeAndroid == PXR_Settings.StereoRenderingModeAndroid.Multiview && firstGfxType == GraphicsDeviceType.OpenGLES2) PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new GraphicsDeviceType[] { GraphicsDeviceType.OpenGLES3 }); if (PlayerSettings.Android.minSdkVersion < AndroidSdkVersions.AndroidApiLevel29) throw new BuildFailedException("Android Minimum API must be set to 29 or higher for PICO Plugin."); base.OnPreprocessBuild(report); } } #if UNITY_2021_3_OR_NEWER internal class PXR_BuildHooks : IPreprocessBuildWithReport, IPostprocessBuildWithReport { public int callbackOrder { get; } private static readonly Dictionary AndroidBootConfigVars = new Dictionary() { { "xr-usable-core-mask-enabled", "1"}, { "xr-require-backbuffer-textures", "0" }, { "xr-hide-memoryless-render-texture", "1" }, { "xr-vulkan-extension-fragment-density-map-enabled", "1"} }; public void OnPreprocessBuild(BuildReport report) { if (report.summary.platformGroup == BuildTargetGroup.Android) { var bootConfig = new BootConfig(report); bootConfig.ReadBootConfig(); foreach (var entry in AndroidBootConfigVars) { bootConfig.SetValueForKey(entry.Key, entry.Value); } bootConfig.WriteBootConfig(); var issues = PXR_ProjectValidationRequired.GetValidationIssues(); foreach (var issue in issues) { if (issue.error) { Debug.LogError($"PXR SDK validation failed: {issue.description}"); throw new BuildFailedException($"There are unresolved PXR configuration errors"); } } } } public void OnPostprocessBuild(BuildReport report) { if (report.summary.platformGroup == BuildTargetGroup.Android) { BootConfig bootConfig = new BootConfig(report); bootConfig.ReadBootConfig(); foreach (KeyValuePair entry in AndroidBootConfigVars) { bootConfig.ClearEntryForKeyAndValue(entry.Key, entry.Value); } bootConfig.WriteBootConfig(); } } } /// /// Small utility class for reading, updating and writing boot config. /// internal class BootConfig { private const string XrBootSettingsKey = "xr-boot-settings"; private readonly Dictionary bootConfigSettings; private readonly string buildTargetName; public BootConfig(BuildReport report) { bootConfigSettings = new Dictionary(); buildTargetName = BuildPipeline.GetBuildTargetName(report.summary.platform); } public void ReadBootConfig() { bootConfigSettings.Clear(); string xrBootSettings = EditorUserBuildSettings.GetPlatformSettings(buildTargetName, XrBootSettingsKey); if (!string.IsNullOrEmpty(xrBootSettings)) { 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() { 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()); } } #endif #if UNITY_ANDROID internal class PXR_Manifest : IPostGenerateGradleAndroidProject { public void OnPostGenerateGradleAndroidProject(string path) { if (!PXR_BuildProcessor.IsLoaderExists()) return; string originManifestPath = path + "/src/main/AndroidManifest.xml"; XmlDocument doc = new XmlDocument(); doc.Load(originManifestPath); string manifestTagPath = "/manifest"; string applicationTagPath = manifestTagPath + "/application"; string metaDataTagPath = applicationTagPath + "/meta-data"; string usesPermissionTagName = "uses-permission"; doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.app.type"}},new Dictionary{{"value","vr"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pxr.sdk.version_code"}},new Dictionary{{"value", "5140"}}); doc.InsertAttributeInTargetTag(applicationTagPath,null, new Dictionary() {{"requestLegacyExternalStorage", "true"}}); #if PICO_OPENXR_SDK doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","use.pxr.sdk"}},new Dictionary{{"value", "2"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.sdk.version"}},new Dictionary{{"value","Unity OpenXR "+PXR_Constants.SDKVersion}}); var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android); bool mrPermission = false; foreach (var feature in settings.GetFeatures()) { if (feature is PICOSceneCapture) { if (feature.enabled) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_scene_anchor" } }, new Dictionary { { "value", "1" } }); mrPermission = true; } else { doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_scene_anchor" } }); } } if (feature is PICOSpatialAnchor) { if (feature.enabled) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_spatial_anchor" } }, new Dictionary { { "value", "1" } }); mrPermission = true; } else { doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_spatial_anchor" } }); } } if (feature is PICOSpatialMesh) { if (feature.enabled) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mesh_anchor" } }, new Dictionary { { "value", "1" } }); mrPermission = true; } else { doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mesh_anchor" } }); } } #if XR_HAND if (feature is HandTracking) { if (feature.enabled) { if (PXR_OpenXRProjectSetting.GetProjectConfig().isHandTracking) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.HAND_TRACKING" } }); if (PXR_OpenXRProjectSetting.GetProjectConfig().handTrackingSupportType == HandTrackingSupport.HandsOnly) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }, new Dictionary { { "value", "1" } }); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }); } else { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }, new Dictionary { { "value", "1" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }, new Dictionary { { "value", "1" } }); } doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "Hand_Tracking_HighFrequency" } }, new Dictionary { { "value", PXR_OpenXRProjectSetting.GetProjectConfig().highFrequencyHand ? "1" : "0" } }); } else { doc.RemoveNameValueElementInTag(manifestTagPath, usesPermissionTagName, "android:name", "com.picovr.permission.HAND_TRACKING"); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "Hand_Tracking_HighFrequency" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }, new Dictionary { { "value", "1" } }); } } else { doc.RemoveNameValueElementInTag(manifestTagPath, usesPermissionTagName, "android:name", "com.picovr.permission.HAND_TRACKING"); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "Hand_Tracking_HighFrequency" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }, new Dictionary { { "value", "1" } }); } } } #else } doc.RemoveNameValueElementInTag(manifestTagPath, usesPermissionTagName, "android:name", "com.picovr.permission.HAND_TRACKING"); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "Hand_Tracking_HighFrequency" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }, new Dictionary { { "value", "1" } }); #endif if (PXR_OpenXRProjectSetting.GetProjectConfig().isEyeTracking) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.EYE_TRACKING" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "picovr.software.eye_tracking" } }, new Dictionary { { "value", "1" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "eyetracking_calibration" } }, new Dictionary { { "value", PXR_OpenXRProjectSetting.GetProjectConfig().isEyeTrackingCalibration ? "true" : "false" } }); } else { doc.RemoveNameValueElementInTag(manifestTagPath, usesPermissionTagName, "android:name", "com.picovr.permission.EYE_TRACKING"); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "picovr.software.eye_tracking" } }); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "eyetracking_calibration" } }); } if (PXR_OpenXRProjectSetting.GetProjectConfig().MRSafeguard) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mr_safeguard" } }, new Dictionary { { "value", PXR_OpenXRProjectSetting.GetProjectConfig().MRSafeguard ? "1" : "0" } }); } else { doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mr_safeguard" } }); } if (mrPermission) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.SPATIAL_DATA" } }); } else { doc.RemoveNameValueElementInTag(manifestTagPath, usesPermissionTagName, "android:name", "com.picovr.permission.SPATIAL_DATA"); } doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "pvr.app.splash" } }, new Dictionary { { "value", PXR_OpenXRProjectSetting.GetProjectConfig().GetSystemSplashScreen(path) } }); #else var settings = PXR_XmlTools.GetSettings(); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","use.pxr.sdk"}},new Dictionary{{"value", "1"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.sdk.version"}},new Dictionary{{"value","XR Platform_"+PXR_Constants.SDKVersion}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","enable_cpt"}},new Dictionary{{"value",PXR_ProjectSetting.GetProjectConfig().useContentProtect ? "1" : "0"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","Enable_AdaptiveHandModel"}},new Dictionary {{"value",PXR_ProjectSetting.GetProjectConfig().adaptiveHand ? "1" : "0" }}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","Hand_Tracking_HighFrequency"}},new Dictionary {{"value",PXR_ProjectSetting.GetProjectConfig().highFrequencyHand ? "1" : "0" }}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","rendering_mode"}},new Dictionary{{"value",((int)settings.stereoRenderingModeAndroid).ToString()}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","display_rate"}},new Dictionary{{"value",((int)settings.systemDisplayFrequency).ToString()}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","color_Space"}},new Dictionary{{"value",QualitySettings.activeColorSpace == ColorSpace.Linear ? "1" : "0"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","MRCsupport"}},new Dictionary{{"value",PXR_ProjectSetting.GetProjectConfig().openMRC ? "1" : "0" }}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.LateLatching"}}, new Dictionary {{"value",PXR_ProjectSetting.GetProjectConfig().latelatching ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.LateLatchingDebug"}}, new Dictionary {{"value", PXR_ProjectSetting.GetProjectConfig().latelatching && PXR_ProjectSetting.GetProjectConfig().latelatchingDebug ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.app.splash"} },new Dictionary{{"value",settings.GetSystemSplashScreen(path)}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","PICO.swift.feature"}},new Dictionary{{"value",PXR_ProjectSetting.GetProjectConfig().bodyTracking ? "1" : "0" }}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","adaptive_resolution"}},new Dictionary{{"value",PXR_ProjectSetting.GetProjectConfig().adaptiveResolution ? "1" : "0" }}); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mr_safeguard" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().mrSafeguard ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_vst" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().videoSeeThrough ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_anchor" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().spatialAnchor ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "mr_map_mgr_auto_start" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().spatialAnchor ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_spatial_anchor" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().spatialAnchor ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_cloud_anchor" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().sharedAnchor ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mesh_anchor" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().spatialMesh ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_scene_anchor" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().sceneCapture ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "pvr.SuperResolution" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().superResolution ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "pvr.NormalSharpening" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().normalSharpening ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "pvr.QualitySharpening" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().qualitySharpening ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "pvr.FixedFoveatedSharpening" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().fixedFoveatedSharpening ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "pvr.SelfAdaptiveSharpening" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().selfAdaptiveSharpening ? "1" : "0" } }); doc.CreateElementInTag(manifestTagPath,usesPermissionTagName,new Dictionary{{"name","android.permission.WRITE_SETTINGS"}}); if (PXR_ProjectSetting.GetProjectConfig().eyeTracking || PXR_ProjectSetting.GetProjectConfig().enableETFR) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.EYE_TRACKING" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "picovr.software.eye_tracking" } }, new Dictionary { { "value", "1" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "eyetracking_calibration" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().eyetrackingCalibration ? "true" : "false" } }); } if (PXR_ProjectSetting.GetProjectConfig().spatialAnchor || PXR_ProjectSetting.GetProjectConfig().sceneCapture || PXR_ProjectSetting.GetProjectConfig().spatialMesh || PXR_ProjectSetting.GetProjectConfig().sharedAnchor) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.SPATIAL_DATA" } }); } if (PXR_ProjectSetting.GetProjectConfig().handTracking) { if (PXR_ProjectSetting.GetProjectConfig().handTrackingSupportType==HandTrackingSupport.HandsOnly) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }, new Dictionary { { "value", "1" } }); doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }); doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.HAND_TRACKING" } }); } else { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }, new Dictionary { { "value", "1" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }, new Dictionary { { "value", "1" } }); doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.HAND_TRACKING" } }); } } else { doc.RemoveAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "handtracking" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "controller" } }, new Dictionary { { "value", "1" } }); doc.RemoveNameValueElementInTag(manifestTagPath, usesPermissionTagName, "android:name", "com.picovr.permission.HAND_TRACKING"); } if (PXR_ProjectSetting.GetProjectConfig().faceTracking) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "com.picovr.permission.FACE_TRACKING" } }); } if (PXR_ProjectSetting.GetProjectConfig().lipsyncTracking) { doc.CreateElementInTag(manifestTagPath, usesPermissionTagName, new Dictionary { { "name", "android.permission.RECORD_AUDIO" } }); } if (PXR_ProjectSetting.GetProjectConfig().faceTracking) { doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "picovr.software.face_tracking" } }, new Dictionary { { "value", "false/true" } }); } #endif doc.Save(originManifestPath); } public int callbackOrder { get { return 10000; } } } #endif public static class PXR_XmlTools { static readonly string androidURI = "http://schemas.android.com/apk/res/android"; public static void InsertAttributeInTargetTag(this XmlDocument doc, string tagPath, Dictionary filterDic, Dictionary attributeDic) { XmlElement targetElement = null; if (filterDic == null) targetElement = doc.SelectSingleNode(tagPath) as XmlElement; else { XmlNodeList nodeList = doc.SelectNodes(tagPath); if (nodeList != null) { foreach (XmlNode node in nodeList) { if (FilterCheck(node as XmlElement, filterDic)) { targetElement = node as XmlElement; break; } } } } if (targetElement == null) { string parentPath = tagPath.Substring(0, tagPath.LastIndexOf("/")); string tagName = tagPath.Substring(tagPath.LastIndexOf("/") + 1); foreach (var item in attributeDic) filterDic.Add(item.Key, item.Value); doc.CreateElementInTag(parentPath, tagName, filterDic); } else UpdateOrCreateAttribute(targetElement, attributeDic); } public static void RemoveAttributeInTargetTag(this XmlDocument doc, string tagPath, Dictionary filterDic) { if (filterDic != null) { XmlNodeList nodeList = doc.SelectNodes(tagPath); if (nodeList != null) { foreach (XmlNode node in nodeList) { if (FilterCheck(node as XmlElement, filterDic)) { node.ParentNode?.RemoveChild(node); break; } } } } } public static void RemoveNameValueElementInTag(this 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); } } } } public static void CreateElementInTag(this XmlDocument doc, string parentPath, string tagName, Dictionary attributeDic) { XmlNode parentNode = doc.SelectSingleNode(parentPath); if (parentNode == null) { return; } foreach (XmlNode childNode in parentNode.ChildNodes) { if (childNode.NodeType == XmlNodeType.Element) { if (FilterCheck((XmlElement)childNode, attributeDic)) return; } } XmlElement newElement = doc.CreateElement(tagName); UpdateOrCreateAttribute(newElement, attributeDic); parentNode.AppendChild(newElement); } private static bool FilterCheck(XmlElement element, Dictionary filterDic) { foreach (var filterCase in filterDic) { string caseValue = element.GetAttribute(filterCase.Key, androidURI); if (String.IsNullOrEmpty(caseValue) || caseValue != filterCase.Value) return false; } return true; } private static void UpdateOrCreateAttribute(XmlElement element, Dictionary attributeDic) { foreach (var attributeItem in attributeDic) { element.SetAttribute(attributeItem.Key, androidURI, attributeItem.Value); } } public static PXR_Settings GetSettings() { PXR_Settings settings = null; #if UNITY_EDITOR UnityEditor.EditorBuildSettings.TryGetConfigObject("Unity.XR.PXR.Settings", out settings); #endif #if UNITY_ANDROID && !UNITY_EDITOR settings = PXR_Settings.settings; #endif return settings; } } }