using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml; using UnityEditor; using UnityEditor.Android; using UnityEditor.XR.Management; using UnityEngine; using UnityEngine.XR.Management; namespace Unity.XR.Management.AndroidManifest.Editor { /// /// Class that retrieves Android manifest entries required by classes that implement the IAndroidManifestEntryProvider interface. /// internal class AndroidManifestProcessor { private static readonly string k_androidManifestFileName = "AndroidManifest.xml"; #if UNITY_2021_1_OR_NEWER private static readonly string k_xrLibraryDirectoryName = "xrmanifest.androidlib"; private static readonly string k_xrLibraryManifestRelativePath = string.Join(Path.DirectorySeparatorChar.ToString(), k_xrLibraryDirectoryName, k_androidManifestFileName); #endif private static readonly List k_activityElementPath = new List() { "manifest", "application", "activity" }; private readonly string m_unityLibraryManifestFilePath; #if UNITY_2021_1_OR_NEWER private readonly string m_xrPackageManifestTemplateFilePath; private readonly string m_xrLibraryManifestFilePath; #endif private readonly XRManagerSettings m_xrSettings; internal AndroidManifestProcessor(string gradleProjectPath, XRManagerSettings settings) { m_unityLibraryManifestFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), gradleProjectPath, "src", "main", k_androidManifestFileName); m_xrSettings = settings; } #if UNITY_2021_1_OR_NEWER internal AndroidManifestProcessor( string gradleProjectPath, string xrManagementPackagePath, XRManagerSettings settings) { m_xrPackageManifestTemplateFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), xrManagementPackagePath, k_xrLibraryManifestRelativePath); m_xrLibraryManifestFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), gradleProjectPath, k_xrLibraryManifestRelativePath); m_unityLibraryManifestFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), gradleProjectPath, "src", "main", k_androidManifestFileName); m_xrSettings = settings; } #endif internal bool UseActivityAppEntry { get; set; } = true; internal bool UseGameActivityAppEntry { get; set; } = false; internal void ProcessManifestRequirements(List manifestProviders) { var activeLoaders = GetActiveLoaderList(); // Get manifest entries from providers var manifestRequirements = manifestProviders .Select(provider => provider.ProvideManifestRequirement()) .OfType() .Distinct() // Requirements can apply to different platforms, so we filter out those whose loaders aren't currently active .Where(requirement => requirement.SupportedXRLoaders.Any(loaderType => activeLoaders.Contains(loaderType))) .ToList(); var mergedRequiredElements = MergeElements( manifestRequirements .SelectMany(requirement => requirement.OverrideElements)); var elementsToBeRemoved = manifestRequirements .SelectMany(requirement => requirement.RemoveElements) .OfType(); #if UNITY_2021_1_OR_NEWER // The intent-filter elements are not merged by default, // so we separate them from the XR manifest to add them later. // Otherwise, the application won't load correctly. var newRequiredElements = manifestRequirements .SelectMany(requirement => requirement.NewElements) .Where(element => !element.ElementPath.Contains("activity")); var newActivityElements = manifestRequirements .SelectMany(requirement => requirement.NewElements) .Where(element => element.ElementPath.Contains("activity") && !element.ElementPath.Contains("intent-filter")); var newIntentElements = manifestRequirements .SelectMany(requirement => requirement.NewElements) .Where(element => element.ElementPath.Contains("intent-filter")); { var xrLibraryManifest = new AndroidManifestDocument(m_xrPackageManifestTemplateFilePath); var unityLibraryManifest = new AndroidManifestDocument(m_unityLibraryManifestFilePath); // Create activity elements depending on the selected application entry in Player Settings if (UseActivityAppEntry) { xrLibraryManifest.CreateNewElement(k_activityElementPath, new Dictionary { { "name", "com.unity3d.player.UnityPlayerActivity" } }); } if (UseGameActivityAppEntry) { xrLibraryManifest.CreateNewElement(k_activityElementPath, new Dictionary { { "name", "com.unity3d.player.UnityPlayerGameActivity" } }); } AddExportedAttributeToActivity(xrLibraryManifest, newIntentElements); if (UseActivityAppEntry && UseGameActivityAppEntry) { xrLibraryManifest.CreateElements(newRequiredElements); // Add all related activity elements to each element foreach (var newActivityElement in newActivityElements) { xrLibraryManifest.CreateNewElementInAllPaths(newActivityElement.ElementPath, newActivityElement.Attributes); } xrLibraryManifest.OverrideElements(mergedRequiredElements); // Add all related activity elements to each element foreach (var newIntentElement in newIntentElements) { unityLibraryManifest.CreateNewElementInAllPaths(newIntentElement.ElementPath, newIntentElement.Attributes); } unityLibraryManifest.RemoveElements(elementsToBeRemoved); } else { xrLibraryManifest.CreateElements(newRequiredElements); xrLibraryManifest.CreateElements(newActivityElements); xrLibraryManifest.OverrideElements(mergedRequiredElements); unityLibraryManifest.CreateElements(newIntentElements, false); // Add the intents in the unity library manifest unityLibraryManifest.RemoveElements(elementsToBeRemoved); } // Write updated manifests xrLibraryManifest.SaveAs(m_xrLibraryManifestFilePath); unityLibraryManifest.Save(); } #else var newRequiredElements = manifestRequirements .SelectMany(requirement => requirement.NewElements); { var manifest = new AndroidManifestDocument(m_unityLibraryManifestFilePath); manifest.CreateNewElement(k_activityElementPath, new Dictionary { { "name", "com.unity3d.player.UnityPlayerActivity" } }); manifest.CreateElements(newRequiredElements); manifest.OverrideElements(mergedRequiredElements); manifest.RemoveElements(elementsToBeRemoved); // Write manifest into project's library path manifest.Save(); } #endif } /// /// Merges the elements of given of type based on their . /// Their key-value pair attributes are deduped and merged into a single element. /// /// of type containing all elements to be merged. /// Filtered of type with unique elements. private IEnumerable MergeElements(IEnumerable source) { return source .GroupBy( (requirement) => requirement.ElementPath, (requirement) => requirement, (elementPath, groupedRequirements) => { var mergedAttributes = groupedRequirements .SelectMany(requirement => requirement.Attributes) .Distinct() .ToDictionary(pair => pair.Key, pair => pair.Value); return new ManifestElement { ElementPath = elementPath, Attributes = mergedAttributes }; }); } private List GetActiveLoaderList() { if (!m_xrSettings) { // No loaders active, don't throw error Debug.LogWarning("No XR Manager settings found, manifest entries will not be updated."); return new List(); } return m_xrSettings.activeLoaders .Select(loader => loader.GetType()) .ToList(); } private void AddExportedAttributeToActivity( AndroidManifestDocument xrLibraryManifest, IEnumerable newIntentElements) { if (newIntentElements.Any()) { // Add exported attribute to all activities in the XR library manifest, as required by the Android manifest var activityPath = string.Join("/", k_activityElementPath); var activityNodes = xrLibraryManifest.SelectNodes(activityPath); foreach (var activity in activityNodes) { XmlAttribute exportedAttribute = xrLibraryManifest.CreateAttribute("android:exported", "http://schemas.android.com/apk/res/android"); exportedAttribute.Value = "true"; ((XmlElement)activity).Attributes.Append(exportedAttribute); } } } } }