/******************************************************************************* 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; namespace Unity.XR.PXR.Editor { public class PXR_BuildProcessor : XRBuildHelper { public override string BuildSettingsKey { get { return "Unity.XR.PXR.Settings"; } } public static bool IsLoaderExists() { XRGeneralSettings generalSettings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Android); if (generalSettings == null) return false; var assignedSettings = generalSettings.AssignedSettings; if (assignedSettings == null) return false; #if UNITY_2021_1_OR_NEWER foreach (XRLoader loader in assignedSettings.activeLoaders) { if (loader is PXR_Loader) return true; } #else foreach (XRLoader loader in assignedSettings.loaders) { if (loader is PXR_Loader) return true; } #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.assetPath.Contains("pxr_api-release.aar")) { plugin.SetIncludeInBuildDelegate((path) => { return IsLoaderExists(); }); } } } 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.AndroidApiLevel26) throw new BuildFailedException("Android Minimum API must be set to 26 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() { }; 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(); } } 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"; var settings = PXR_XmlTools.GetSettings(); doc.InsertAttributeInTargetTag(applicationTagPath,null, new Dictionary() {{"requestLegacyExternalStorage", "true"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.app.type"}},new Dictionary{{"value","vr"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","pvr.sdk.version"}},new Dictionary{{"value","XR Platform_2.3.4"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","enable_cpt"}},new Dictionary{{"value",PXR_ProjectSetting.GetProjectConfig().useContentProtect ? "1" : "0"}}); doc.InsertAttributeInTargetTag(metaDataTagPath,new Dictionary{{"name","handtracking"}},new Dictionary {{"value",PXR_ProjectSetting.GetProjectConfig().handTracking ? "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", "mr_map_require" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().spatialAnchor ? "1" : "0" } }); doc.InsertAttributeInTargetTag(metaDataTagPath, new Dictionary { { "name", "enable_mr" } }, new Dictionary { { "value", PXR_ProjectSetting.GetProjectConfig().spatialAnchor ? "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.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().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" } }); } 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 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; } } }