Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
## [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
Show file tree
Hide file tree
Showing 24 changed files with 1,375 additions and 189 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Editor/AndroidManifest.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs
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 Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

202 changes: 202 additions & 0 deletions Editor/AndroidManifest/AndroidManifestDocument.cs
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);
}
}
}
}
11 changes: 11 additions & 0 deletions Editor/AndroidManifest/AndroidManifestDocument.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 54ccdac

Please sign in to comment.