using System; using System.IO; using System.Text; using System.Xml; using UnityEditor; using UnityEditor.Build.Reporting; using UnityEditor.XR.OpenXR.Features; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; namespace Unity.XR.OpenXR.Features.PICOSupport { internal class PICOModifyAndroidManifest : OpenXRFeatureBuildHooks { public override int callbackOrder => 1; public override Type featureType => typeof(PICOFeature); protected override void OnPreprocessBuildExt(BuildReport report) { } protected override void OnPostGenerateGradleAndroidProjectExt(string path) { var androidManifest = new AndroidManifest(GetManifestPath(path)); androidManifest.AddPICOMetaData(path); androidManifest.Save(); } protected override void OnPostprocessBuildExt(BuildReport report) { } private string _manifestFilePath; private string GetManifestPath(string basePath) { if (!string.IsNullOrEmpty(_manifestFilePath)) return _manifestFilePath; var pathBuilder = new StringBuilder(basePath); pathBuilder.Append(Path.DirectorySeparatorChar).Append("src"); pathBuilder.Append(Path.DirectorySeparatorChar).Append("main"); pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml"); _manifestFilePath = pathBuilder.ToString(); return _manifestFilePath; } private class AndroidXmlDocument : XmlDocument { private string m_Path; protected XmlNamespaceManager nsMgr; public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android"; public AndroidXmlDocument(string path) { m_Path = path; using (var reader = new XmlTextReader(m_Path)) { reader.Read(); Load(reader); } nsMgr = new XmlNamespaceManager(NameTable); nsMgr.AddNamespace("android", AndroidXmlNamespace); } public string Save() { return SaveAs(m_Path); } public string SaveAs(string path) { using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) { writer.Formatting = Formatting.Indented; Save(writer); } return path; } } private class AndroidManifest : AndroidXmlDocument { private readonly XmlElement ApplicationElement; private readonly XmlElement ManifestElement; public AndroidManifest(string path) : base(path) { ManifestElement = SelectSingleNode("/manifest") as XmlElement; ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement; } private XmlAttribute CreateOrUpdateAndroidAttribute(string key, string value) { XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace); attr.Value = value; return attr; } private void CreateOrUpdateAndroidPermissionData(string name) { XmlNodeList nodeList = ManifestElement.SelectNodes("uses-permission"); foreach (XmlNode node in nodeList) { if (node != null) { // Update existing nodes if (node.Attributes != null && name.Equals(node.Attributes[0].Value)) { return; } } } // Create new node var md = ManifestElement.AppendChild(CreateElement("uses-permission")); md.Attributes.Append(CreateOrUpdateAndroidAttribute("name", name.ToString())); } private void DeleteAndroidPermissionData(string name) { XmlNodeList nodeList = ManifestElement.SelectNodes("uses-permission"); foreach (XmlNode node in nodeList) { if (node != null) { // Delete existing nodes if (node.Attributes != null && name.Equals(node.Attributes[0].Value)) { node.ParentNode?.RemoveChild(node); return; } } } } private void CreateOrUpdateAndroidMetaData(string name, string value) { XmlNodeList nodeList = ApplicationElement.SelectNodes("meta-data"); foreach (XmlNode node in nodeList) { if (node != null) { // Update existing nodes if (node.Attributes != null && name.Equals(node.Attributes[0].Value)) { node.Attributes[0].Value = name; node.Attributes[1].Value = value; return; } } } // Create new node var md = ApplicationElement.AppendChild(CreateElement("meta-data")); md.Attributes.Append(CreateOrUpdateAndroidAttribute("name", name.ToString())); md.Attributes.Append(CreateOrUpdateAndroidAttribute("value", value.ToString())); } private void DeleteAndroidMetaData(string name) { XmlNodeList nodeList = ApplicationElement.SelectNodes("meta-data"); foreach (XmlNode node in nodeList) { if (node != null) { // Delete existing nodes if (node.Attributes != null && name.Equals(node.Attributes[0].Value)) { node.ParentNode?.RemoveChild(node); return; } } } } internal void AddPICOMetaData(string path) { CreateOrUpdateAndroidMetaData("pvr.app.type", "vr"); CreateOrUpdateAndroidMetaData("pvr.sdk.version", "Unity OpenXR "+PICOFeature.SDKVersion); CreateOrUpdateAndroidMetaData("pxr.sdk.version_code", "5800"); if (PICOProjectSetting.GetProjectConfig().isHandTracking) { CreateOrUpdateAndroidPermissionData("com.picovr.permission.HAND_TRACKING"); CreateOrUpdateAndroidMetaData("handtracking", "1"); } else { DeleteAndroidPermissionData("com.picovr.permission.HAND_TRACKING"); DeleteAndroidMetaData("handtracking"); } if (PICOProjectSetting.GetProjectConfig().isEyeTracking) { CreateOrUpdateAndroidPermissionData("com.picovr.permission.EYE_TRACKING"); CreateOrUpdateAndroidMetaData("picovr.software.eye_tracking", "1"); CreateOrUpdateAndroidMetaData("eyetracking_calibration", PICOProjectSetting.GetProjectConfig().isEyeTrackingCalibration ? "true" : "false"); } else { DeleteAndroidPermissionData("com.picovr.permission.EYE_TRACKING"); DeleteAndroidMetaData("picovr.software.eye_tracking"); DeleteAndroidMetaData("eyetracking_calibration"); } var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android); bool mrPermission = false; foreach (var feature in settings.GetFeatures()) { if (feature is PICOSceneCapture) { if (feature.enabled) { CreateOrUpdateAndroidMetaData("enable_scene_anchor", "1"); mrPermission = true; } else { DeleteAndroidMetaData("enable_scene_anchor"); } } if (feature is PICOSpatialAnchor) { if (feature.enabled) { CreateOrUpdateAndroidMetaData("enable_spatial_anchor", "1"); mrPermission = true; } else { DeleteAndroidMetaData("enable_spatial_anchor"); } } if (feature is PICOSpatialMesh) { if (feature.enabled) { CreateOrUpdateAndroidMetaData("enable_mesh_anchor", "1"); mrPermission = true; } else { DeleteAndroidMetaData("enable_mesh_anchor"); } } } if (PICOProjectSetting.GetProjectConfig().MRSafeguard) { CreateOrUpdateAndroidMetaData("enable_mr_safeguard", PICOProjectSetting.GetProjectConfig().MRSafeguard ? "1" : "0"); } else { DeleteAndroidMetaData("enable_mr_safeguard"); } if (mrPermission) { CreateOrUpdateAndroidPermissionData("com.picovr.permission.SPATIAL_DATA"); } else { DeleteAndroidPermissionData("com.picovr.permission.SPATIAL_DATA"); } CreateOrUpdateAndroidMetaData("pvr.app.splash", PICOProjectSetting.GetProjectConfig().GetSystemSplashScreen(path)); } } } }