diff --git a/CHANGELOG.md b/CHANGELOG.md index d9591d7..7e41d01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [4.4.0] - 2023-03-30 + +### Added +- Created `AndroidManifestProcessor` as an Android manifest management system for XR packages and providers that target platforms based on Android. + +### Removed +- AndroidManifest.xml is no longer deleted after each Android build. + ## [4.3.3] - 2023-02-14 ### Fixed diff --git a/Editor/AndroidManifest.meta b/Editor/AndroidManifest.meta new file mode 100644 index 0000000..9a2ee73 --- /dev/null +++ b/Editor/AndroidManifest.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7ab74fff4fadf1841820ded42d6ecd4c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs b/Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs new file mode 100644 index 0000000..40a508a --- /dev/null +++ b/Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEditor.Android; +using UnityEditor.XR.Management; +using UnityEngine.XR.Management; + +namespace Unity.XR.Management.AndroidManifest.Editor +{ + /// + /// Class that receives the build event when building an Android Gradle project, + /// so the manifest element processing can be executed. + /// + internal class AndroidManifestBuildEventReceiver : IPostGenerateGradleAndroidProject + { + + public int callbackOrder => 1; + + public void OnPostGenerateGradleAndroidProject(string gradleProjectPath) + { + var processor = CreateManifestProcessor(gradleProjectPath); + + var manifestProviders = GetManifestProviders(); + processor.ProcessManifestRequirements(manifestProviders); + } + + private AndroidManifestProcessor CreateManifestProcessor(string gradleProjectPath) + { +#if UNITY_2021_1_OR_NEWER + var xrManagementPackagePath = EditorUtilities.GetPackagePath("com.unity.xr.management"); + return new AndroidManifestProcessor(gradleProjectPath, xrManagementPackagePath, GetXRManagerSettings()); +#else + return new AndroidManifestProcessor(gradleProjectPath, GetXRManagerSettings()); +#endif + } + + /// + /// Finds all implementation of in the assemblies, + /// and creates instances for each type into a single collection. + /// + /// collection of instances. All contained objects are unique. + private List GetManifestProviders() + { + return TypeCache + .GetTypesDerivedFrom() + .Where(type => !type.IsInterface && !type.IsAbstract && !type.IsNestedPrivate) + .Select(providerType => Activator.CreateInstance(providerType)) // Instantiate providers + .OfType() + .Distinct() + .ToList(); + } + + private XRManagerSettings GetXRManagerSettings() + { + if (XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Android)) + { + return XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Android).AssignedSettings; + } + + return null; + } + } +} diff --git a/Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs.meta b/Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs.meta new file mode 100644 index 0000000..c0a7689 --- /dev/null +++ b/Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d19006cf3476fe8418e1018605a8ef99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AndroidManifest/AndroidManifestDocument.cs b/Editor/AndroidManifest/AndroidManifestDocument.cs new file mode 100644 index 0000000..1b5c3d9 --- /dev/null +++ b/Editor/AndroidManifest/AndroidManifestDocument.cs @@ -0,0 +1,202 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Text; +using System.Xml; + +namespace Unity.XR.Management.AndroidManifest.Editor +{ + /// + /// This class holds information that should be displayed in an Editor tooltip for a given package. + /// + internal class AndroidManifestDocument : XmlDocument + { + internal static readonly string k_androidXmlNamespace = "http://schemas.android.com/apk/res/android"; + + private readonly string m_Path; + private readonly XmlNamespaceManager m_nsMgr; + + internal AndroidManifestDocument() + { + m_nsMgr = new XmlNamespaceManager(NameTable); + m_nsMgr.AddNamespace("android", k_androidXmlNamespace); + } + + internal AndroidManifestDocument(string path) + { + m_Path = path; + + using (var reader = new XmlTextReader(m_Path)) + { + reader.Read(); + Load(reader); + } + + m_nsMgr = new XmlNamespaceManager(NameTable); + m_nsMgr.AddNamespace("android", k_androidXmlNamespace); + } + + internal string Save() + { + return SaveAs(m_Path); + } + + internal string SaveAs(string path) + { + // ensure the folder exists so that the XmlTextWriter doesn't fail + Directory.CreateDirectory(Path.GetDirectoryName(path)); + using (var writer = new XmlTextWriter(path, new UTF8Encoding(false))) + { + writer.Formatting = Formatting.Indented; + Save(writer); + } + + return path; + } + + internal void CreateNewElement(List path, Dictionary attributes) + { + // Look up for closest parent node to new leaf node + XmlElement parentNode, node = null; + int nextNodeIndex = -1; + do + { + nextNodeIndex++; + parentNode = node; + node = (XmlElement)(parentNode == null ? + SelectSingleNode(path[nextNodeIndex]) : + parentNode.SelectSingleNode(path[nextNodeIndex])); + } while (node != null && nextNodeIndex < path.Count - 1); + + // If nodes are missing between root and leaf, fill out hierarchy including leaf node + for (int i = nextNodeIndex; i < path.Count; i++) + { + node = CreateElement(path[i]); + parentNode.AppendChild(node); + parentNode = node; + } + + // Apply attributes to leaf node + foreach (var attributePair in attributes) + { + node.SetAttribute(attributePair.Key, k_androidXmlNamespace, attributePair.Value); + } + } + + internal void CreateNewElementIfDoesntExist(List path, Dictionary attributes) + { + var existingNodeElements = SelectNodes(string.Join("/", path)); + foreach(XmlElement element in existingNodeElements) + { + if (CheckNodeAttributesMatch(element, attributes)) + { + return; + } + } + + CreateNewElement(path, attributes); + } + + internal void CreateOrOverrideElement(List path, Dictionary attributes) + { + // Look up for leaf node or closest + XmlElement parentNode, node = null; + int nextNodeIndex = -1; + do + { + nextNodeIndex++; + parentNode = node; + node = (XmlElement)(parentNode == null ? + SelectSingleNode(path[nextNodeIndex]) : + parentNode.SelectSingleNode(path[nextNodeIndex])); + } while (node != null && nextNodeIndex < path.Count - 1); + + // If nodes are missing between root and leaf, fill out hierarchy including leaf node + if (node == null) + { + for (int i = nextNodeIndex; i < path.Count; i++) + { + node = CreateElement(path[i]); + parentNode.AppendChild(node); + parentNode = node; + } + } + + // Apply attributes to leaf node + foreach (var attributePair in attributes) + { + node.SetAttribute(attributePair.Key, k_androidXmlNamespace, attributePair.Value); + } + } + + internal void RemoveMatchingElement(List elementPath, Dictionary attributes) + { + var xmlNodeList = SelectNodes(string.Join("/", elementPath)); + + foreach (XmlElement node in xmlNodeList) + { + if (CheckNodeAttributesMatch(node, attributes)) + { + node.ParentNode?.RemoveChild(node); + } + } + } + + private bool CheckNodeAttributesMatch(XmlNode node, Dictionary attributes) + { + var nodeAttributes = node.Attributes; + foreach (XmlAttribute attribute in nodeAttributes) + { + var rawAttributeName = attribute.Name.Split(':').Last(); + if (!attributes.Contains(new KeyValuePair(rawAttributeName, attribute.Value))) + { + return false; + } + } + return true; + } + + internal void CreateElements(IEnumerable newElements, bool allowDuplicates = true) + { + if(allowDuplicates) + { + foreach (var requirement in newElements) + { + this + .CreateNewElement( + requirement.ElementPath, requirement.Attributes); + } + } + else + { + foreach (var requirement in newElements) + { + this + .CreateNewElementIfDoesntExist( + requirement.ElementPath, requirement.Attributes); + } + } + } + + internal void OverrideElements(IEnumerable overrideElements) + { + foreach (var requirement in overrideElements) + { + this + .CreateOrOverrideElement( + requirement.ElementPath, requirement.Attributes); + } + } + + internal void RemoveElements(IEnumerable removableElements) + { + foreach (var requirement in removableElements) + { + this + .RemoveMatchingElement( + requirement.ElementPath, requirement.Attributes); + } + } + } +} diff --git a/Editor/AndroidManifest/AndroidManifestDocument.cs.meta b/Editor/AndroidManifest/AndroidManifestDocument.cs.meta new file mode 100644 index 0000000..5f37435 --- /dev/null +++ b/Editor/AndroidManifest/AndroidManifestDocument.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 155fb79913e46e546926a3cad34ee561 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AndroidManifest/AndroidManifestProcessor.cs b/Editor/AndroidManifest/AndroidManifestProcessor.cs new file mode 100644 index 0000000..c57f361 --- /dev/null +++ b/Editor/AndroidManifest/AndroidManifestProcessor.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +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 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 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("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); + + xrLibraryManifest.CreateElements(newRequiredElements); + 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.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(); + } + } +} diff --git a/Editor/AndroidManifest/AndroidManifestProcessor.cs.meta b/Editor/AndroidManifest/AndroidManifestProcessor.cs.meta new file mode 100644 index 0000000..d8c28cc --- /dev/null +++ b/Editor/AndroidManifest/AndroidManifestProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1797bfd7905e844ab00f68dca3ed050 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AndroidManifest/IAndroidManifestRequirementProvider.cs b/Editor/AndroidManifest/IAndroidManifestRequirementProvider.cs new file mode 100644 index 0000000..17f54b7 --- /dev/null +++ b/Editor/AndroidManifest/IAndroidManifestRequirementProvider.cs @@ -0,0 +1,19 @@ +namespace Unity.XR.Management.AndroidManifest.Editor +{ + /// + /// Interface for classes providing Android manifest entries. + /// + /// All implementers must be top level classes for the Android manifest processsor to call them + /// Nested classes aren't supported. + /// + public interface IAndroidManifestRequirementProvider + { + /// + /// Provides a object with the required Android manifest elements and its attributes needed to be + /// added, overriden or removed. + /// + /// + /// with element requirements data. + ManifestRequirement ProvideManifestRequirement(); + } +} diff --git a/Editor/AndroidManifest/IAndroidManifestRequirementProvider.cs.meta b/Editor/AndroidManifest/IAndroidManifestRequirementProvider.cs.meta new file mode 100644 index 0000000..5d6a5a0 --- /dev/null +++ b/Editor/AndroidManifest/IAndroidManifestRequirementProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3b45a81d07011564297a01edd9f8bbfb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AndroidManifest/ManifestElement.cs b/Editor/AndroidManifest/ManifestElement.cs new file mode 100644 index 0000000..5596bc4 --- /dev/null +++ b/Editor/AndroidManifest/ManifestElement.cs @@ -0,0 +1,43 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Unity.XR.Management.AndroidManifest.Editor +{ + /// + /// This class holds information for a single Android manifest element, including its path and attributes. + /// + public class ManifestElement + { + /// + /// + /// List of element names representing the full XML path to the element. It must include last the element that this object represents. + /// + /// + /// + /// + /// + /// The order in which the elements are added is important, + /// as each list member represents an XML element name in the order, + /// so specifying a list as { "manifest", "application" } is not the same + /// as { "application", "manifest"}. + /// + /// + /// Example for accessing a meta-data element: + /// + /// new ManifestElement { + /// ElementPath = new List() { + /// "manifest", "application", "meta-data" + /// } + /// } + /// + /// + /// + public List ElementPath { get; set; } + + /// + /// Dictionary of Name-Value pairs of the represented element's attributes. + /// + public Dictionary Attributes { get; set; } + } +} diff --git a/Editor/AndroidManifest/ManifestElement.cs.meta b/Editor/AndroidManifest/ManifestElement.cs.meta new file mode 100644 index 0000000..ed33196 --- /dev/null +++ b/Editor/AndroidManifest/ManifestElement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 101cdf2d24ab75f4cb29ac9cb47e8209 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/AndroidManifest/ManifestRequirement.cs b/Editor/AndroidManifest/ManifestRequirement.cs new file mode 100644 index 0000000..83c3ab0 --- /dev/null +++ b/Editor/AndroidManifest/ManifestRequirement.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; + +namespace Unity.XR.Management.AndroidManifest.Editor +{ + /// + /// This class contains lists of Android manifest elements that need to be added, overriden or removed from the application manifest. + /// + public class ManifestRequirement : IEquatable + { + /// + /// Set of supported types by these requirements. + /// If none of the listed loaders is active at the moment of building, the requirements will be ignored. + /// + public HashSet SupportedXRLoaders { get; set; } = new HashSet(); + + /// + /// List of elements that will be added to the Android manifest. + /// Each entry represents a single element within its specified node path, and it won't overwrite or override any other element to be added. + /// + public List NewElements { get; set; } = new List(); + + /// + /// List of elements whose attirbutes will be merged or overriden with existing the Android manifest elements. + /// If the manifest element doesn't exist in the file, it will be created. + /// + public List OverrideElements { get; set; } = new List(); + + /// + /// List of elements which will be removed from the Android manifest. + /// Entries not found will be ignored. + /// Only entries that specify the same attributes and its respective values in the manifest will be taken in account for deletion. + /// + public List RemoveElements { get; set; } = new List(); + + /// + public override bool Equals(object obj) + { + if (obj == null) + { + return false; + } + + return obj is ManifestRequirement && Equals(obj as ManifestRequirement); + } + + /// + public bool Equals(ManifestRequirement other) + { + return other != null && + ((NewElements == null && other.NewElements == null) || (NewElements != null && NewElements.Equals(other.NewElements))) && + ((OverrideElements == null && other.OverrideElements == null) || (OverrideElements != null && OverrideElements.Equals(other.OverrideElements))) && + ((RemoveElements == null && other.RemoveElements == null) || (RemoveElements != null && RemoveElements.Equals(other.RemoveElements))); + } + + /// + public override int GetHashCode() + { +#if UNITY_2021_3_OR_NEWER + return HashCode.Combine(NewElements, OverrideElements, RemoveElements); +#else + // Unfortunately, 2020.3 compiles against .NET 2.0, which doesn't include the more reasonable HashCode class. + // Still, a hash function is needed for comparing these objects, so going on with the option outlined here: + // https://stackoverflow.com/questions/1646807/quick-and-simple-hash-code-combinations + int factor = 31; + int hash = 17; + hash = hash * factor + (NewElements != null ? NewElements.GetHashCode() : 0); + hash = hash * factor + (OverrideElements != null ? OverrideElements.GetHashCode() : 0); + hash = hash * factor + (RemoveElements != null ? RemoveElements.GetHashCode() : 0); + return hash; +#endif // UNITY_2021_3_OR_NEWER + } + } +} diff --git a/Editor/AndroidManifest/ManifestRequirement.cs.meta b/Editor/AndroidManifest/ManifestRequirement.cs.meta new file mode 100644 index 0000000..e2f26e6 --- /dev/null +++ b/Editor/AndroidManifest/ManifestRequirement.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fac60b1ea386ed14da6c50ad574e0ab0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/EditorUtilities.cs b/Editor/EditorUtilities.cs index 79403e6..f008b6e 100644 --- a/Editor/EditorUtilities.cs +++ b/Editor/EditorUtilities.cs @@ -114,5 +114,21 @@ internal static ScriptableObject CreateScriptableObjectInstance(string typeName, return null; } + /// + /// Retrieves the path of the XR Management package's Android manifest XML template. + /// + /// Package name in Java convention (eg com.package.module). + /// of the XML file path. + internal static string GetPackagePath(string packageName) + { +#if UNITY_2021_3_OR_NEWER + var xrManagementPackageInfo = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages() + .Where(package => package.name == packageName) + .First(); + return xrManagementPackageInfo.resolvedPath; +#else + throw new System.NotSupportedException("XR Management package couldn't be found, please make sure you have the XR Management package added to your project through the Package Management window."); +#endif // UNITY_2021_3_OR_NEWER + } } } diff --git a/Editor/XRGeneralBuildProcessor.cs b/Editor/XRGeneralBuildProcessor.cs index 2109e2c..55edd9a 100644 --- a/Editor/XRGeneralBuildProcessor.cs +++ b/Editor/XRGeneralBuildProcessor.cs @@ -92,7 +92,7 @@ internal void WriteBootConfig() } } - class XRGeneralBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport, IPostGenerateGradleAndroidProject + class XRGeneralBuildProcessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport { public static readonly string kPreInitLibraryKey = "xrsdk-pre-init-library"; @@ -116,10 +116,6 @@ public int callbackOrder get { return s_CallbackOrder; } } - // This has to be static as the OnPostprocessBuild and OnPostGenerateGradleAndroidProject - // callbacks are made on different instances. - internal static string s_ManifestPath; - void CleanOldSettings() { BuildHelpers.CleanOldSettings(); @@ -157,9 +153,9 @@ internal void OnPreprocessBuildImpl(in GUID buildGuid, in BuildTargetGroup targe // dirty later builds with assets that may not be needed or are out of date. CleanOldSettings(); - // Fix for [1378643](https://fogbugz.unity3d.com/f/cases/1378643/) - if (!TryGetSettingsPerBuildTarget(out var buildTargetSettings)) - return; + var buildTargetSettings = XRGeneralSettingsPerBuildTarget.GetOrCreate(); + if (!buildTargetSettings) + return; XRGeneralSettings settings = buildTargetSettings.SettingsForBuildTarget(targetGroup); if (settings == null) @@ -266,34 +262,6 @@ public void OnPostprocessBuild(BuildReport report) // Always remember to cleanup preloaded assets after build to make sure we don't // dirty later builds with assets that may not be needed or are out of date. CleanOldSettings(); - - // This is done to clean up XR-provider-specific manifest entries from Android builds when - // the incremental build system fails to rebuild the manifest, as there is currently - // (2023-09-27) no way to mark the manifest as "dirty." - CleanupAndroidManifest(); - } - - public void CleanupAndroidManifest() - { - if (!string.IsNullOrEmpty(s_ManifestPath)) - { - try - { - File.Delete(s_ManifestPath); - } - catch (Exception e) - { - // This only fails if the file can't be deleted; it is quiet if the file does not exist. - Debug.LogWarning("Failed to clean up AndroidManifest file located at " + s_ManifestPath + " : " + e.ToString()); - } - } - s_ManifestPath = ""; - } - - public void OnPostGenerateGradleAndroidProject(string path) - { - const string k_ManifestPath = "/src/main/AndroidManifest.xml"; - s_ManifestPath = path + k_ManifestPath; } } } diff --git a/Editor/XRGeneralSettingsPerBuildTarget.cs b/Editor/XRGeneralSettingsPerBuildTarget.cs index 622a684..24a61cf 100644 --- a/Editor/XRGeneralSettingsPerBuildTarget.cs +++ b/Editor/XRGeneralSettingsPerBuildTarget.cs @@ -3,10 +3,8 @@ using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading; using UnityEngine; using UnityEngine.XR.Management; -using UnityEditor.PackageManager; using UnityEditor.XR.Management.Metadata; @@ -61,28 +59,12 @@ static XRGeneralSettingsPerBuildTarget CreateAssetSynchronized() AssetDatabase.CreateAsset(generalSettings, assetPath); AssetDatabase.SaveAssets(); } - EditorBuildSettings.AddConfigObject(XRGeneralSettings.k_SettingsKey, generalSettings, true); return generalSettings; } internal static XRGeneralSettingsPerBuildTarget GetOrCreate() - { - TryFindSettingsAsset(out var generalSettings); - generalSettings = generalSettings ?? CreateAssetSynchronized(); - - var buildTargetSettings = generalSettings.SettingsForBuildTarget( - EditorUserBuildSettings.selectedBuildTargetGroup - ); - var activeLoaderCount = (buildTargetSettings?.AssignedSettings?.activeLoaders?.Count ?? 0); - if (IsInURPGraphicsTest() && activeLoaderCount == 0) - { - Debug.Log("In URP graphics test and no loaders selected. Disabling 'Initialize XR On Startup'"); - buildTargetSettings.InitManagerOnStart = false; - } - - return generalSettings; - } + => TryFindSettingsAsset(out var generalSettings) ? generalSettings : CreateAssetSynchronized(); // Simple class to give us updates when the asset database changes. class AssetCallbacks : AssetPostprocessor @@ -153,43 +135,6 @@ internal static bool ContainsLoaderForAnyBuildTarget(string loaderTypeName) return false; } - - // Temporary workaround for a graphics issue - static bool? s_IsInURPGraphicsTest = null; - internal static bool IsInURPGraphicsTest() - { - if (!s_IsInURPGraphicsTest.HasValue) - { - s_IsInURPGraphicsTest = false; - - const string kGfxFramework = "com.unity.testing.urp"; - - var request = Client.List(offlineMode: true); - while (request.IsCompleted == false) - { - Thread.Yield(); - } - - if (request.Status != StatusCode.Success) - { - throw new InvalidOperationException( - $"Unexpected package query failure searching for {kGfxFramework}" - ); - } - - // if we find the com.unity.testing.urp package then assume we're in the URP - // graphics test. - foreach (var result in request.Result) - { - if (result.packageId.StartsWith(kGfxFramework)) - { - s_IsInURPGraphicsTest = true; - break; - } - } - } - return s_IsInURPGraphicsTest.Value; - } #endif /// @@ -305,9 +250,7 @@ public void OnAfterDeserialize() /// The instance of assigned to the key, or null if not. public static XRGeneralSettings XRGeneralSettingsForBuildTarget(BuildTargetGroup targetGroup) { - XRGeneralSettingsPerBuildTarget buildTargetSettings = null; - EditorBuildSettings.TryGetConfigObject(XRGeneralSettings.k_SettingsKey, out buildTargetSettings); - if (buildTargetSettings == null) + if (!TryFindSettingsAsset(out var buildTargetSettings)) return null; return buildTargetSettings.SettingsForBuildTarget(targetGroup); diff --git a/Tests/Editor/AndroidManifestTests.cs b/Tests/Editor/AndroidManifestTests.cs new file mode 100644 index 0000000..f1194cf --- /dev/null +++ b/Tests/Editor/AndroidManifestTests.cs @@ -0,0 +1,615 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using Unity.XR.Management.AndroidManifest.Editor; +using UnityEditor; +using UnityEditor.XR.Management; +using UnityEngine; +using UnityEngine.XR.Management; + +public class AndroidManifestTests +{ + private string tempProjectPath; + private string xrManifestTemplateFilePath; + private string xrLibraryManifestFilePath; + private string unityLibraryManifestFilePath; + private DirectoryInfo dirInfo; + private XRManagerSettings mockXrSettings; + private Type supportedLoaderType; + + [SetUp] + public void SetUp() + { + tempProjectPath = FileUtil.GetUniqueTempPathInProject(); + dirInfo = Directory.CreateDirectory(tempProjectPath); + + var xrPackagePath = dirInfo.CreateSubdirectory(string.Join(Path.DirectorySeparatorChar.ToString(), "xrPackage", "xrmanifest.androidlib")); + var xrLibraryPath = dirInfo.CreateSubdirectory("xrmanifest.androidlib"); + var unityLibraryPath = dirInfo.CreateSubdirectory(string.Join(Path.DirectorySeparatorChar.ToString(), "src", "main")); + + xrManifestTemplateFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), xrPackagePath.FullName, "AndroidManifest.xml"); + xrLibraryManifestFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), xrLibraryPath.FullName, "AndroidManifest.xml"); + unityLibraryManifestFilePath = string.Join(Path.DirectorySeparatorChar.ToString(), unityLibraryPath.FullName, "AndroidManifest.xml"); + + CreateMockManifestDocument(xrManifestTemplateFilePath); + CreateMockManifestDocument(xrLibraryManifestFilePath); + CreateMockManifestDocument(unityLibraryManifestFilePath); + + mockXrSettings = ScriptableObject.CreateInstance(); + supportedLoaderType = typeof(MockXrLoader); + mockXrSettings.TrySetLoaders(new List + { + ScriptableObject.CreateInstance() + }); + } + + [TearDown] + public void TearDown() + { + dirInfo.Delete(true); + ScriptableObject.DestroyImmediate(mockXrSettings); + } + + [Test] + public void AndroidManifestProcessor_AddOneNewManifestElement() + { + var processor = CreateProcessor(); + + // Initialize data + var newElementPath = new List { "manifest", "application", "meta-data" }; + var newElementAttributes = new Dictionary() + { + { "name", "custom-data" }, + { "value", "test-data" }, + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + NewElements = new List() + { + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + } + } + }) + }; + + // Execute + processor.ProcessManifestRequirements(providers); + + // Validate + var updatedLibraryManifest = GetXrLibraryManifest(); + var nodes = updatedLibraryManifest.SelectNodes(string.Join("/", newElementPath)); + Assert.AreEqual( + 1, + nodes.Count, + "Additional elements exist in the Manifest when expecting 1"); + + var attributeList = nodes[0].Attributes; + Assert.AreEqual( + newElementAttributes.Count, + attributeList.Count, + "Attribute count in element doesn't match expected count"); + + AssertAttributesAreEqual(nodes[0].Name, newElementAttributes, attributeList); + } + + [Test] + public void AndroidManifestProcessor_AddTwoNewManifestElements() + { + var processor = CreateProcessor(); + + // Initialize data + var newElementPath = new List { "manifest", "application", "meta-data" }; + var newElementAttributes = new Dictionary() + { + { "name", "custom-data" }, + { "value", "test-data" }, + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + NewElements = new List() + { + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + }, + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + } + } + }) + }; + + // Execute + processor.ProcessManifestRequirements(providers); + + // Validate + var updatedLibraryManifest = GetXrLibraryManifest(); + var nodes = updatedLibraryManifest.SelectNodes(string.Join("/", newElementPath)); + Assert.AreEqual( + 2, + nodes.Count, + "Additional elements exist in the Manifest when expecting 2"); + + foreach(XmlElement node in nodes) + { + var attributeList = node.Attributes; + Assert.AreEqual( + newElementAttributes.Count, + attributeList.Count, + "Attribute count in element doesn't match expected count"); + + AssertAttributesAreEqual(node.Name, newElementAttributes, attributeList); + } + } + + [Test] + public void AndroidManifestProcessor_CreateSingleNewManifestElementFromTwoOverridenElements() + { + // Use the Assert class to test conditions + var processor = CreateProcessor(); + + // Initialize data + var overrideElementPath = new List { "manifest", "application", "activity" }; + var overrideElement1Attributes = new Dictionary(); + var overrideElement2Attributes = new Dictionary(); + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + OverrideElements = new List() + { + new ManifestElement() + { + ElementPath = overrideElementPath, + Attributes = overrideElement1Attributes + }, + new ManifestElement() + { + ElementPath = overrideElementPath, + Attributes = overrideElement2Attributes + } + } + }) + }; + + // Execute + processor.ProcessManifestRequirements(providers); + + // Validate + var updatedLibraryManifest = GetXrLibraryManifest(); + var nodes = updatedLibraryManifest.SelectNodes(string.Join("/", overrideElementPath)); + Assert.AreEqual( + 1, + nodes.Count, + "Additional elements exist in the Manifest when expecting 1"); + + var attributeList = nodes[0].Attributes; + var expectedElementAttrributes = MergeDictionaries(overrideElement1Attributes, overrideElement2Attributes); + Assert.AreEqual( + expectedElementAttrributes.Count, + attributeList.Count, + $"Attribute count in element doesn't match expected {expectedElementAttrributes.Count}"); + + AssertAttributesAreEqual(nodes[0].Name, expectedElementAttrributes, attributeList); + } + + + [Test] + public void AndroidManifestProcessor_UpdateExistingElementWithOverridenElement() + { + // Use the Assert class to test conditions + var processor = CreateProcessor(); + + // Initialize data + var overrideElementPath = new List { "manifest", "application", "activity" }; + var existingElementAttributes = new Dictionary() + { + { "name", "com.test.app" } + }; + var overrideElementAttributes = new Dictionary() + { + { "isGame", "true" }, + { "testOnly", "true" }, + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + OverrideElements = new List() + { + new ManifestElement() + { + ElementPath = overrideElementPath, + Attributes = overrideElementAttributes + } + } + }) + }; + + // Prepare test document + var libManifest = GetXrLibraryManifest(); + libManifest.CreateNewElement(overrideElementPath, existingElementAttributes); + libManifest.Save(); + + // Execute + processor.ProcessManifestRequirements(providers); + + // Validate + var updatedLibraryManifest = GetXrLibraryManifest(); + var nodes = updatedLibraryManifest.SelectNodes(string.Join("/", overrideElementPath)); + Assert.AreEqual( + 1, + nodes.Count, + "Additional elements exist in the Manifest when expecting 1"); + + var attributeList = nodes[0].Attributes; + var expectedElementAttrributes = MergeDictionaries(existingElementAttributes, overrideElementAttributes); + Assert.AreEqual( + expectedElementAttrributes.Count, + attributeList.Count, + $"Attribute count {attributeList.Count} in element doesn't match expected {expectedElementAttrributes.Count}"); + + AssertAttributesAreEqual(nodes[0].Name, expectedElementAttrributes, attributeList); + } + + [Test] + public void AndroidManifestProcessor_DeleteExistingManifestElement() + { + var processor = CreateProcessor(); + + // Initialize data + var deletedElementPath = new List { "manifest", "uses-permission" }; + var deletedElementAttributes = new Dictionary() + { + { "name", "BLUETOOTH" } + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + RemoveElements = new List() + { + new ManifestElement() + { + ElementPath = deletedElementPath, + Attributes = deletedElementAttributes + } + } + }) + }; + + // Prepare test document + var appManifest = GetUnityLibraryManifest(); + appManifest.CreateNewElement(deletedElementPath, deletedElementAttributes); + appManifest.Save(); + + // Execute + processor.ProcessManifestRequirements(providers); + + // Validate + var updatedAppManifest = GetXrLibraryManifest(); + var removedElementPath = string.Join("/", deletedElementPath); + var removedNodes = updatedAppManifest.SelectNodes(removedElementPath); + Assert.AreEqual( + 0, + removedNodes.Count, + $"Expected element in path \"{removedElementPath}\" wasn't deleted"); + } + + [Test] + public void AndroidManifestProcessor_DontModifyManifestIfNoSupportedLoadersAdded() + { + var processor = CreateProcessor(); + + // Initialize data + var newElementPath = new List { "manifest", "application", "meta-data" }; + var newElementAttributes = new Dictionary() + { + { "name", "custom-data" }, + { "value", "test-data" }, + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + typeof(object) // Dummy object representing an inactive loader + }, + NewElements = new List() + { + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + } + } + }) + }; + + // Execute + processor.ProcessManifestRequirements(providers); + + // Validate + var updatedLibraryManifest = GetXrLibraryManifest(); + var nodes = updatedLibraryManifest.SelectNodes(string.Join("/", newElementPath)); + Assert.AreEqual( + 0, + nodes.Count, + "Elements exist in the Manifest when expecting 0"); + } + +#if UNITY_2021_1_OR_NEWER + [Test] + public void AndroidManifestProcessor_AddNewIntentsOnlyInUnityLibraryManifest() + { + var processor = CreateProcessor(); + + // Initialize data + var newElementPath = + new List { "manifest", "application", "activity", "intent-filter", "category" }; + var newElementAttributes = new Dictionary() + { + { "name", "com.oculus.intent.category.VR" } + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + NewElements = new List() + { + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + } + } + }) + }; + + // Execute + processor.ProcessManifestRequirements(providers); + + var elementPath = string.Join("/", newElementPath); + + // Validate that the intent is created in Unity library manifest + var unityLibManifest = GetUnityLibraryManifest(); + var addedNodes = unityLibManifest.SelectNodes(elementPath); + Assert.AreEqual( + 1, + addedNodes.Count, + $"Expected new element in path \"{elementPath}\" in Unity Library manifest"); + + // Validate that the intent isn't created in XR Library manifest + var xrLibManifest = GetXrLibraryManifest(); + var emptyNodes = xrLibManifest.SelectNodes(elementPath); + Assert.AreEqual( + 0, + emptyNodes.Count, + $"Expected no new element in path \"{elementPath}\" in XR Library manifest"); + } + + [Test] + public void AndroidManifestProcessor_KeepOnlyOneIntentOfTheSameType() + { + var processor = CreateProcessor(); + + // Initialize data + var newElementPath = + new List { "manifest", "application", "activity", "intent-filter", "category" }; + var newElementAttributes = new Dictionary() + { + { "name", "com.oculus.intent.category.VR" } + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + NewElements = new List() + { + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + } + } + }) + }; + + // Prepare test document + var appManifest = GetUnityLibraryManifest(); + appManifest.CreateNewElement(newElementPath, newElementAttributes); + appManifest.Save(); + + // Execute + processor.ProcessManifestRequirements(providers); + + var elementPath = string.Join("/", newElementPath); + + // Validate that only one intent of the same kind is in the manifest + var unityLibManifest = GetUnityLibraryManifest(); + var addedNodes = unityLibManifest.SelectNodes(elementPath); + Assert.AreEqual( + 1, + addedNodes.Count, + $"Expected only 1 element in path \"{elementPath}\" in Unity Library manifest"); + + } + + [Test] + public void AndroidManifestProcessor_AddManyIntentsOfTheSameTypeButKeepOnlyOne() + { + var processor = CreateProcessor(); + + // Initialize data + var newElementPath = + new List { "manifest", "application", "activity", "intent-filter", "category" }; + var newElementAttributes = new Dictionary() + { + { "name", "com.oculus.intent.category.VR" } + }; + var providers = new List() + { + new MockManifestRequirementProvider(new ManifestRequirement + { + SupportedXRLoaders = new HashSet + { + supportedLoaderType + }, + NewElements = new List() + { + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + }, + new ManifestElement() + { + ElementPath = newElementPath, + Attributes = newElementAttributes + } + } + }) + }; + + // Execute + processor.ProcessManifestRequirements(providers); + + var elementPath = string.Join("/", newElementPath); + + // Validate that only one intent of the same kind is in the manifest + var unityLibManifest = GetUnityLibraryManifest(); + var addedNodes = unityLibManifest.SelectNodes(elementPath); + Assert.AreEqual( + 1, + addedNodes.Count, + $"Expected only 1 element in path \"{elementPath}\" in Unity Library manifest"); + + } +#endif + + private AndroidManifestDocument GetXrLibraryManifest() + { +#if UNITY_2021_1_OR_NEWER + return new AndroidManifestDocument(xrLibraryManifestFilePath); +#else + // Unity 2020 and lower use the same manifest for XR entries as the rest of the app + return GetUnityLibraryManifest(); +#endif + } + + private AndroidManifestDocument GetUnityLibraryManifest() + { + return new AndroidManifestDocument(unityLibraryManifestFilePath); + } + + private AndroidManifestProcessor CreateProcessor() + { +#if UNITY_2021_1_OR_NEWER + return new AndroidManifestProcessor( + tempProjectPath, + tempProjectPath, + mockXrSettings); +#else + return new AndroidManifestProcessor(tempProjectPath, mockXrSettings); +#endif + } + + private void CreateMockManifestDocument(string filePath) + { + var manifestDocument = new AndroidManifestDocument(); + var manifestNode = manifestDocument.CreateElement("manifest"); + manifestNode.SetAttribute("xmlns:android", "http://schemas.android.com/apk/res/android"); + manifestDocument.AppendChild(manifestNode); + var applicationNode = manifestDocument.CreateElement("application"); + manifestNode.AppendChild(applicationNode); + manifestDocument.SaveAs(filePath); + } + + private void AssertAttributesAreEqual( + string elementName, + Dictionary expectedAttributes, + XmlAttributeCollection attributes) + { + foreach (XmlAttribute attrib in attributes) + { + var attributeName = attrib.Name.Split(':').Last(); // Values are returned with preffixed namespace name, pick only the attribute name + if (!expectedAttributes.Contains(new KeyValuePair(attributeName, attrib.Value))) + { + Assert.Fail($"Unexpected attribute \"{attrib.Name}\" " + + $"with value \"{attrib.Value}\" found in element {elementName}"); + } + } + } + + private Dictionary MergeDictionaries(Dictionary dict1, Dictionary dict2) + { + return new List> { dict1, dict2 } + .SelectMany(dict => dict) + .ToDictionary(pair => pair.Key, pair => pair.Value); + } + + private class MockManifestRequirementProvider : IAndroidManifestRequirementProvider + { + private readonly ManifestRequirement requirement; + + public MockManifestRequirementProvider(ManifestRequirement mockRequirments) + { + requirement = mockRequirments; + } + + public ManifestRequirement ProvideManifestRequirement() + { + return requirement; + } + } + + private class MockXrLoader : XRLoader + { + public override T GetLoadedSubsystem() + { + throw new NotImplementedException(); + } + } +} diff --git a/Tests/Editor/AndroidManifestTests.cs.meta b/Tests/Editor/AndroidManifestTests.cs.meta new file mode 100644 index 0000000..7b0bb2f --- /dev/null +++ b/Tests/Editor/AndroidManifestTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fe69636be56b48041abf9fd83457a282 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/BuildTests.cs b/Tests/Editor/BuildTests.cs index e2af711..574ebb7 100644 --- a/Tests/Editor/BuildTests.cs +++ b/Tests/Editor/BuildTests.cs @@ -440,94 +440,5 @@ private XRManagerSettings GetXRManagerSettings() .AssignedSettings; } } - - [TestFixture(BuildTargetGroup.Android, BuildTarget.Android)] - class TestAndroidManifestReset - { - public class PostGradleCallback : IPostGenerateGradleAndroidProject - { - public int callbackOrder - { - get { return XRGeneralBuildProcessor.s_CallbackOrder + 1; } - } - - public void OnPostGenerateGradleAndroidProject(string path) - { - s_onPostGenerateGradleAndroidProjectEvent?.Invoke(this, path); - } - } - - private readonly BuildTargetGroup m_BuildTargetGroup; - private readonly BuildTarget m_BuildTarget; - - private XRGeneralSettingsPerBuildTarget m_OldBuildTargetSettings; - - private static event EventHandler s_onPostGenerateGradleAndroidProjectEvent; - - public TestAndroidManifestReset(BuildTargetGroup group, BuildTarget target) - { - m_BuildTargetGroup = group; - m_BuildTarget = target; - } - - [Test] - public void DoesCleanUpAfterBuild() - { - const string k_ScenePath = "TestScene.unity"; - const string k_BuildFolder = "./build"; - const string k_BuildFile = k_BuildFolder + "/build.apk"; - - if (!BuildPipeline.IsBuildTargetSupported(m_BuildTargetGroup, m_BuildTarget)) - { - Debug.LogWarning( - string.Format( - "Test platform lacks {0}/{1} build target support", - m_BuildTargetGroup, - m_BuildTarget - ) - ); - return; - } - - Scene currentScene = SceneManager.GetActiveScene(); - currentScene.name = "TestScene"; - EditorSceneManager.SaveScene(currentScene, k_ScenePath); - - EventHandler gradleCallback = (object sender, string s) => - { - Assert.True(File.Exists(XRGeneralBuildProcessor.s_ManifestPath)); - }; - s_onPostGenerateGradleAndroidProjectEvent += gradleCallback; - - // if the project is blank and no package name was set, this prevents the test from - // failing - var packageID = PlayerSettings.GetApplicationIdentifier(m_BuildTargetGroup); - bool shouldChange = packageID?.StartsWith("com.DefaultCompany") ?? false; - if (shouldChange) { - PlayerSettings.SetApplicationIdentifier(m_BuildTargetGroup, "com.unity.test"); - } - - Directory.CreateDirectory(k_BuildFolder); - BuildPipeline.BuildPlayer( - new BuildPlayerOptions{ - locationPathName = k_BuildFile, - options = BuildOptions.None, - scenes = new string[]{k_ScenePath}, - target = m_BuildTarget, - targetGroup = m_BuildTargetGroup - } - ); - - Assert.False(File.Exists(XRGeneralBuildProcessor.s_ManifestPath)); - - File.Delete(k_ScenePath); - Directory.Delete(k_BuildFolder, true); - if (shouldChange) { - PlayerSettings.SetApplicationIdentifier(m_BuildTargetGroup, packageID); - } - - s_onPostGenerateGradleAndroidProjectEvent -= gradleCallback; - } - } } #endif //UNITY_EDITOR_WIN || UNITY_EDITOR_OSX diff --git a/package.json b/package.json index 2b4d391..06d49fa 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.xr.management", "displayName": "XR Plugin Management", - "version": "4.3.3", + "version": "4.4.0", "unity": "2019.4", "unityRelease": "15f1", "description": "Package that provides simple management of XR plug-ins. Manages and offers help with loading, initialization, settings, and build support for XR plug-ins.", @@ -23,15 +23,15 @@ "repository": { "url": "https://github.cds.internal.unity3d.com/unity/xr.sdk.management.git", "type": "git", - "revision": "11953d1846a70bfe37146dc4655a8b2d93d32b95" + "revision": "6fefbe0e3deca93f6eb53e66ca554b58f2acf0d8" }, "_upm": { - "changelog": "### Fixed\n- Settings and Loader names with numbers will now keep the numbers for the generated asset names." + "changelog": "### Added\n- Created `AndroidManifestProcessor` as an Android manifest management system for XR packages and providers that target platforms based on Android.\n\n### Removed\n- AndroidManifest.xml is no longer deleted after each Android build." }, "upmCi": { - "footprint": "e47471cb7ebbf7c240bc0eccde6e675b16d3c6a9" + "footprint": "eda98e50f28e9cbade3f5ad2433dc3b55cbe5eb5" }, - "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.xr.management@4.3/manual/index.html", + "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.xr.management@4.4/manual/index.html", "samples": [ { "displayName": "Example XR Management implementation", diff --git a/xrmanifest.androidlib.meta b/xrmanifest.androidlib.meta new file mode 100644 index 0000000..68f6d6f --- /dev/null +++ b/xrmanifest.androidlib.meta @@ -0,0 +1,71 @@ +fileFormatVersion: 2 +guid: 8443d3621b17161459d890b196695db3 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + AndroidSharedLibraryType: Executable + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + userData: + assetBundleName: + assetBundleVariant: diff --git a/xrmanifest.androidlib/AndroidManifest.xml b/xrmanifest.androidlib/AndroidManifest.xml new file mode 100644 index 0000000..80eaab9 --- /dev/null +++ b/xrmanifest.androidlib/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/xrmanifest.androidlib/AndroidManifest.xml.meta b/xrmanifest.androidlib/AndroidManifest.xml.meta new file mode 100644 index 0000000..2bb4217 --- /dev/null +++ b/xrmanifest.androidlib/AndroidManifest.xml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2223823ee8acbf942995707617e70ce1 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: