-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## [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.
- Loading branch information
Unity Technologies
committed
Mar 30, 2023
1 parent
8fe972a
commit 54ccdac
Showing
24 changed files
with
1,375 additions
and
189 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
64 changes: 64 additions & 0 deletions
64
Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// Class that receives the build event when building an Android Gradle project, | ||
/// so the manifest element processing can be executed. | ||
/// </summary> | ||
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 | ||
} | ||
|
||
/// <summary> | ||
/// Finds all implementation of <see cref="IAndroidManifestRequirementProvider"/> in the assemblies, | ||
/// and creates instances for each type into a single collection. | ||
/// </summary> | ||
/// <returns><see cref="System.Collections.Generic.List{T}"/> collection of <see cref="IAndroidManifestRequirementProvider"/> instances. All contained objects are unique.</returns> | ||
private List<IAndroidManifestRequirementProvider> GetManifestProviders() | ||
{ | ||
return TypeCache | ||
.GetTypesDerivedFrom<IAndroidManifestRequirementProvider>() | ||
.Where(type => !type.IsInterface && !type.IsAbstract && !type.IsNestedPrivate) | ||
.Select(providerType => Activator.CreateInstance(providerType)) // Instantiate providers | ||
.OfType<IAndroidManifestRequirementProvider>() | ||
.Distinct() | ||
.ToList(); | ||
} | ||
|
||
private XRManagerSettings GetXRManagerSettings() | ||
{ | ||
if (XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Android)) | ||
{ | ||
return XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(BuildTargetGroup.Android).AssignedSettings; | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// This class holds information that should be displayed in an Editor tooltip for a given package. | ||
/// </summary> | ||
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<string> path, Dictionary<string, string> 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<string> path, Dictionary<string, string> attributes) | ||
{ | ||
var existingNodeElements = SelectNodes(string.Join("/", path)); | ||
foreach(XmlElement element in existingNodeElements) | ||
{ | ||
if (CheckNodeAttributesMatch(element, attributes)) | ||
{ | ||
return; | ||
} | ||
} | ||
|
||
CreateNewElement(path, attributes); | ||
} | ||
|
||
internal void CreateOrOverrideElement(List<string> path, Dictionary<string, string> 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<string> elementPath, Dictionary<string, string> 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<string, string> attributes) | ||
{ | ||
var nodeAttributes = node.Attributes; | ||
foreach (XmlAttribute attribute in nodeAttributes) | ||
{ | ||
var rawAttributeName = attribute.Name.Split(':').Last(); | ||
if (!attributes.Contains(new KeyValuePair<string, string>(rawAttributeName, attribute.Value))) | ||
{ | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
internal void CreateElements(IEnumerable<ManifestElement> 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<ManifestElement> overrideElements) | ||
{ | ||
foreach (var requirement in overrideElements) | ||
{ | ||
this | ||
.CreateOrOverrideElement( | ||
requirement.ElementPath, requirement.Attributes); | ||
} | ||
} | ||
|
||
internal void RemoveElements(IEnumerable<ManifestElement> removableElements) | ||
{ | ||
foreach (var requirement in removableElements) | ||
{ | ||
this | ||
.RemoveMatchingElement( | ||
requirement.ElementPath, requirement.Attributes); | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.