// Copyright HTC Corporation All Rights Reserved. using System; using System.IO; using System.Text; using System.Threading; using System.Xml; using UnityEditor; using UnityEditor.Build.Reporting; using UnityEditor.PackageManager; using UnityEditor.XR.OpenXR.Features; using UnityEngine; using UnityEngine.XR.OpenXR; using UnityEngine.XR.OpenXR.Features; using VIVE.OpenXR.Hand; using VIVE.OpenXR.Tracker; // Reference to Unity's OpenXR package namespace VIVE.OpenXR.Editor { internal class ModifyAndroidManifest : OpenXRFeatureBuildHooks { public override int callbackOrder => 1; public override Type featureType => typeof(VIVEFocus3Feature); protected override void OnPreprocessBuildExt(BuildReport report) { } private static string _manifestPath; protected override void OnPostGenerateGradleAndroidProjectExt(string path) { _manifestPath = GetManifestPath(path); var androidManifest = new AndroidManifest(_manifestPath); //androidManifest.AddVIVECategory(); androidManifest.AddViveSDKVersion(); androidManifest.AddUnityVersion(); androidManifest.AddOpenXRPermission(); androidManifest.AddOpenXRFeatures(); androidManifest.Save(); } protected override void OnPostprocessBuildExt(BuildReport report) { if (File.Exists(_manifestPath)) File.Delete(_manifestPath); } 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; } } [InitializeOnLoad] public static class CheckIfSimultaneousInteractionEnabled { const string LOG_TAG = "CheckIfSimultaneousInteractionEnabled "; static StringBuilder m_sb = null; static StringBuilder sb { get { if (m_sb == null) { m_sb = new StringBuilder(); } return m_sb; } } static void DEBUG(StringBuilder msg) { Debug.Log(msg); } internal const string MENU_NAME = "VIVE/Interaction Mode/Enable Simultaneous Interaction"; private static bool m_IsEnabled = false; public static bool IsEnabled { get { return m_IsEnabled; } } static CheckIfSimultaneousInteractionEnabled() { m_IsEnabled = EditorPrefs.GetBool(MENU_NAME, false); /// Delaying until first editor tick so that the menu /// will be populated before setting check state, and /// re-apply correct action EditorApplication.delayCall += () => { PerformAction(m_IsEnabled); }; } [MenuItem(MENU_NAME, priority = 601)] private static void ToggleAction() { /// Toggling action PerformAction(!m_IsEnabled); } public static void PerformAction(bool enabled) { /// Set checkmark on menu item Menu.SetChecked(MENU_NAME, enabled); /// Saving editor state EditorPrefs.SetBool(MENU_NAME, enabled); m_IsEnabled = enabled; sb.Clear().Append(LOG_TAG).Append(m_IsEnabled ? "Enable " : "Disable ").Append("Simultaneous Interaction."); DEBUG(sb); } [MenuItem(MENU_NAME, validate = true, priority = 601)] public static bool ValidateEnabled() { Menu.SetChecked(MENU_NAME, m_IsEnabled); return true; } } private class AndroidManifest : AndroidXmlDocument { private readonly XmlElement IntetnFilterElement; private readonly XmlElement ManifestElement; private readonly XmlElement ApplicationElement; public AndroidManifest(string path) : base(path) { IntetnFilterElement = SelectSingleNode("/manifest/application/activity/intent-filter") as XmlElement; ManifestElement = SelectSingleNode("/manifest") as XmlElement; ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement; } private XmlAttribute CreateAndroidAttribute(string key, string value) { XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace); attr.Value = value; return attr; } private const string FAKE_VERSION = "0.0.0"; private static string SearchPackageVersion(string packageName) { var listRequest = Client.List(true); do { if (listRequest.IsCompleted) { if (listRequest.Result == null) { Debug.Log("List result: is empty"); return FAKE_VERSION; } foreach (var pi in listRequest.Result) { //Debug.Log("List has: " + pi.name + " == " + packageName); if (pi.name == packageName) { Debug.Log("Found " + packageName); return pi.version; } } break; } Thread.Sleep(100); } while (true); return FAKE_VERSION; } internal void AddViveSDKVersion() { var newUsesFeature = CreateElement("meta-data"); newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "com.htc.ViveOpenXR.SdkVersion")); newUsesFeature.Attributes.Append(CreateAndroidAttribute("value", SearchPackageVersion("com.htc.upm.vive.openxr"))); ApplicationElement.AppendChild(newUsesFeature); } internal void AddUnityVersion() { var newUsesFeature = CreateElement("meta-data"); newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "com.htc.vr.content.UnityVersion")); newUsesFeature.Attributes.Append(CreateAndroidAttribute("value", Application.unityVersion)); ApplicationElement.AppendChild(newUsesFeature); } internal void AddVIVECategory() { var md = IntetnFilterElement.AppendChild(CreateElement("category")); md.Attributes.Append(CreateAndroidAttribute("name", "com.htc.intent.category.VRAPP")); } internal void AddOpenXRPermission() { var md = ManifestElement.AppendChild(CreateElement("uses-permission")); md.Attributes.Append(CreateAndroidAttribute("name", "org.khronos.openxr.permission.OPENXR")); md = ManifestElement.AppendChild(CreateElement("uses-permission")); md.Attributes.Append(CreateAndroidAttribute("name", "org.khronos.openxr.permission.OPENXR_SYSTEM")); var md2 = IntetnFilterElement.AppendChild(CreateElement("category")); md2.Attributes.Append(CreateAndroidAttribute("name", "org.khronos.openxr.intent.category.IMMERSIVE_HMD")); } internal void AddOpenXRFeatures() { bool enableViveWristTracker = false; bool enableViveHandTracking = false; var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildTargetGroup.Android); if (null == settings) return; foreach (var feature in settings.GetFeatures()) { if (feature is ViveWristTracker) { enableViveWristTracker = feature.enabled; break; } } foreach (var feature in settings.GetFeatures()) { if (feature is ViveHandTracking) { enableViveHandTracking = feature.enabled; break; ; } } //Debug.Log("enableViveWristTracker " + enableViveWristTracker); if (enableViveWristTracker) { { var newUsesFeature = CreateElement("uses-feature"); newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.handtracking")); newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true")); ManifestElement.AppendChild(newUsesFeature); } { var newUsesFeature = CreateElement("uses-feature"); newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.tracker")); newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true")); ManifestElement.AppendChild(newUsesFeature); } } else if (enableViveHandTracking) { { var newUsesFeature = CreateElement("uses-feature"); newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.handtracking")); newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true")); ManifestElement.AppendChild(newUsesFeature); } } if (CheckIfSimultaneousInteractionEnabled.IsEnabled) { var newUsesFeature = CreateElement("uses-feature"); newUsesFeature.Attributes.Append(CreateAndroidAttribute("name", "wave.feature.simultaneous_interaction")); newUsesFeature.Attributes.Append(CreateAndroidAttribute("required", "true")); ManifestElement.AppendChild(newUsesFeature); } } } } }