Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
## [4.5.0] - 2024-05-21

### Fixed
- Fixed compile errors on Unity versions [2023.1.0f1 - 2023.2.6f1] caused by missing `BuildTargetGroup.VisionOS` enum value.

### Added
- Added Android manifest support for applications that use Game Activity entry point. Note that Game Activities are only supported in projects made with Unity versions 2023.1 onwards.
  • Loading branch information
Unity Technologies committed May 21, 2024
1 parent 7ef72fd commit 4bed75a
Show file tree
Hide file tree
Showing 13 changed files with 631 additions and 46 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.5.0] - 2024-05-21

### Fixed
- Fixed compile errors on Unity versions [2023.1.0f1 - 2023.2.6f1] caused by missing `BuildTargetGroup.VisionOS` enum value.

### Added
- Added Android manifest support for applications that use Game Activity entry point. Note that Game Activities are only supported in projects made with Unity versions 2023.1 onwards.

## [4.4.1] - 2024-01-12

### Added
Expand Down
2 changes: 1 addition & 1 deletion Documentation~/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The root folder of a package is the one where the `package.json` file is located

This version of XR Plug-in Management is compatible with the following versions of the Unity Editor:

* 2019.4.15f1 and later
* 2020.3.0f1 and later

### Known limitations

Expand Down
9 changes: 8 additions & 1 deletion Editor/AndroidManifest/AndroidManifestBuildEventReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ private AndroidManifestProcessor CreateManifestProcessor(string gradleProjectPat
{
#if UNITY_2021_1_OR_NEWER
var xrManagementPackagePath = EditorUtilities.GetPackagePath("com.unity.xr.management");
return new AndroidManifestProcessor(gradleProjectPath, xrManagementPackagePath, GetXRManagerSettings());
var processor = new AndroidManifestProcessor(gradleProjectPath, xrManagementPackagePath, GetXRManagerSettings());

#if UNITY_2023_1_OR_NEWER
processor.UseActivityAppEntry = PlayerSettings.Android.applicationEntry.HasFlag(AndroidApplicationEntry.Activity);
processor.UseGameActivityAppEntry = PlayerSettings.Android.applicationEntry.HasFlag(AndroidApplicationEntry.GameActivity);
#endif

return processor;
#else
return new AndroidManifestProcessor(gradleProjectPath, GetXRManagerSettings());
#endif
Expand Down
135 changes: 126 additions & 9 deletions Editor/AndroidManifest/AndroidManifestDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,108 @@ internal void CreateNewElement(List<string> path, Dictionary<string, string> att
}
}

internal void CreateNewElementIfDoesntExist(List<string> path, Dictionary<string, string> attributes)
internal struct PathNode
{
var existingNodeElements = SelectNodes(string.Join("/", path));
foreach(XmlElement element in existingNodeElements)
public XmlElement parent;
public XmlElement node;
public int DepthIndex;
};

internal void CreateNewElementInAllPaths(List<string> path, Dictionary<string, string> attributes)
{
// Nodes that match the path, just need to add attributes
var nodesToEdit = new List<XmlElement>();
// Nodes that are missing the full path, hence, the desired node and its parents should be created
var incompletePathNodes = new List<PathNode>();

var nodesToCheckQueue = new Queue<PathNode>();
if (DocumentElement.Name.Equals(path.First()))
{
if (CheckNodeAttributesMatch(element, attributes))
nodesToCheckQueue.Enqueue(new PathNode { parent = null, node = DocumentElement, DepthIndex = 0 });
}
else
{
incompletePathNodes.Add(new PathNode { parent = null, node = DocumentElement, DepthIndex = 0 });
}

var targetNodeName = path.Last();

while (nodesToCheckQueue.Any())
{
var currentNode = nodesToCheckQueue.Dequeue();

var nextPathIndex = currentNode.DepthIndex + 1;

if (currentNode.node.ChildNodes.Count == 0 || nextPathIndex >= path.Count)
{
// No children left, needs to add elements to complete the path
var incompletePathNode = targetNodeName.Equals(currentNode.node.Name) ?
// There's a node with the same name, but attributes don't match
new PathNode { node = currentNode.parent, DepthIndex = currentNode.DepthIndex - 1 } :
// No node with the same name, create nodes in the path
currentNode;
incompletePathNodes.Add(incompletePathNode);
continue;
}

// Select only children that match the next path element
var matchingPathChildNodes = currentNode.node.SelectNodes(path[nextPathIndex]);

// Find if a matching child node with the attributes exists
bool foundMatchingNode = false;
foreach (XmlElement childNode in matchingPathChildNodes)
{
if(targetNodeName.Equals(childNode.Name) && CheckNodeAttributesMatch(childNode, attributes))
{
foundMatchingNode = true;
break;
}
}

// If no matching node was found, add all children to the queue to continue searching
if (!foundMatchingNode)
{
foreach (XmlElement childNode in matchingPathChildNodes)
{
nodesToCheckQueue.Enqueue(new PathNode { parent = currentNode.node, node = childNode, DepthIndex = nextPathIndex });
}
}
}

foreach (var incompletePathNode in incompletePathNodes)
{
var parentNode = incompletePathNode.node;
XmlElement newNode = null;
// If nodes are missing between root and leaf, fill out hierarchy including leaf node
for (var i = incompletePathNode.DepthIndex + 1; i < path.Count; i++)
{
newNode = CreateElement(path[i]);
parentNode.AppendChild(newNode);
parentNode = newNode;
}
if (newNode != null)
{
return;
// Only add new nodes that were created
nodesToEdit.Add(parentNode);
}
}

CreateNewElement(path, attributes);
foreach (var node in nodesToEdit)
{
// 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)
{
if (!ElementExists(path, attributes))
{
CreateNewElement(path, attributes);
}
}

internal void CreateOrOverrideElement(List<string> path, Dictionary<string, string> attributes)
Expand Down Expand Up @@ -183,9 +273,22 @@ internal void OverrideElements(IEnumerable<ManifestElement> overrideElements)
{
foreach (var requirement in overrideElements)
{
this
.CreateOrOverrideElement(
requirement.ElementPath, requirement.Attributes);
var matchingNodes = SelectNodes(string.Join("/", requirement.ElementPath));
if (matchingNodes.Count == 0)
{
this.CreateOrOverrideElement(
requirement.ElementPath, requirement.Attributes);
}
else
{
foreach (XmlElement node in matchingNodes)
{
foreach (var attributePair in requirement.Attributes)
{
node.SetAttribute(attributePair.Key, k_androidXmlNamespace, attributePair.Value);
}
}
}
}
}

Expand All @@ -198,5 +301,19 @@ internal void RemoveElements(IEnumerable<ManifestElement> removableElements)
requirement.ElementPath, requirement.Attributes);
}
}

private bool ElementExists(List<string> path, Dictionary<string, string> attributes)
{
var existingNodeElements = SelectNodes(string.Join("/", path));
foreach (XmlElement element in existingNodeElements)
{
if (CheckNodeAttributesMatch(element, attributes))
{
return true;
}
}

return false;
}
}
}
75 changes: 70 additions & 5 deletions Editor/AndroidManifest/AndroidManifestProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using UnityEditor;
using UnityEditor.Android;
using UnityEditor.XR.Management;
Expand All @@ -21,6 +22,7 @@ internal class AndroidManifestProcessor
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<string> k_activityElementPath = new List<string>() { "manifest", "application", "activity" };

private readonly string m_unityLibraryManifestFilePath;
#if UNITY_2021_1_OR_NEWER
Expand Down Expand Up @@ -49,6 +51,9 @@ internal AndroidManifestProcessor(
}
#endif

internal bool UseActivityAppEntry { get; set; } = true;
internal bool UseGameActivityAppEntry { get; set; } = false;

internal void ProcessManifestRequirements(List<IAndroidManifestRequirementProvider> manifestProviders)
{
var activeLoaders = GetActiveLoaderList();
Expand Down Expand Up @@ -76,7 +81,12 @@ internal void ProcessManifestRequirements(List<IAndroidManifestRequirementProvid
// Otherwise, the application won't load correctly.
var newRequiredElements = manifestRequirements
.SelectMany(requirement => requirement.NewElements)
.Where(element => !element.ElementPath.Contains("intent-filter"));
.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"));
Expand All @@ -85,10 +95,46 @@ internal void ProcessManifestRequirements(List<IAndroidManifestRequirementProvid
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);
// Create activity elements depending on the selected application entry in Player Settings
if (UseActivityAppEntry)
{
xrLibraryManifest.CreateNewElement(k_activityElementPath, new Dictionary<string, string> { { "name", "com.unity3d.player.UnityPlayerActivity" } });
}

if (UseGameActivityAppEntry)
{
xrLibraryManifest.CreateNewElement(k_activityElementPath, new Dictionary<string, string> { { "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);
Expand All @@ -100,6 +146,7 @@ internal void ProcessManifestRequirements(List<IAndroidManifestRequirementProvid

{
var manifest = new AndroidManifestDocument(m_unityLibraryManifestFilePath);
manifest.CreateNewElement(k_activityElementPath, new Dictionary<string, string> { { "name", "com.unity3d.player.UnityPlayerActivity" } });

manifest.CreateElements(newRequiredElements);
manifest.OverrideElements(mergedRequiredElements);
Expand Down Expand Up @@ -149,5 +196,23 @@ private List<Type> GetActiveLoaderList()
.Select(loader => loader.GetType())
.ToList();
}

private void AddExportedAttributeToActivity(
AndroidManifestDocument xrLibraryManifest,
IEnumerable<ManifestElement> 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);
}
}
}
}
}
3 changes: 2 additions & 1 deletion Editor/Metadata/KnownPackages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ static List<IXRPackage> InitKnownPackages()
loaderType = "UnityEngine.XR.OpenXR.OpenXRLoader",
supportedBuildTargets = new List<BuildTargetGroup>() {
BuildTargetGroup.Standalone,
BuildTargetGroup.WSA
BuildTargetGroup.WSA,
BuildTargetGroup.Android
}
},
}
Expand Down
7 changes: 6 additions & 1 deletion Editor/Unity.XR.Management.Editor.asmdef
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
"versionDefines": [
{
"name": "Unity",
"expression": "2022.3.15f1",
"expression": "[2022.3.15f1,2023.1)",
"define": "UNITY_XR_VISIONOS_SUPPORTED"
},
{
"name": "Unity",
"expression": "2023.3.0b4",
"define": "UNITY_XR_VISIONOS_SUPPORTED"
}
],
Expand Down
7 changes: 7 additions & 0 deletions Editor/XRSettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct Content
public static readonly GUIContent k_XRConfigurationText = new GUIContent("Information about configuration, tracking and migration can be found below.");
public static readonly GUIContent k_XRConfigurationDocUriText = new GUIContent("View Documentation");
public static readonly Uri k_XRConfigurationUri = new Uri(" https://docs.unity3d.com/Manual/configuring-project-for-xr.html");
public static readonly GUIContent k_EditorTargetPlatform = new GUIContent("Editor Play mode uses Desktop Platform Settings regardless of Active Build Target.");
}

internal static GUIStyle GetStyle(string styleName)
Expand Down Expand Up @@ -163,6 +164,12 @@ private void DisplayLoaderSelectionUI()
EditorGUILayout.PropertyField(initOnStart, Content.k_InitializeOnStart);
EditorGUILayout.Space();

if (BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget) != BuildTargetGroup.Standalone)
{
EditorGUILayout.HelpBox(Content.k_EditorTargetPlatform.text, MessageType.Info);
EditorGUILayout.Space();
}


SerializedProperty loaderProp = serializedSettingsObject.FindProperty("m_LoaderManagerInstance");
var obj = loaderProp.objectReferenceValue;
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This package is primarily intented for XR Package authors to provide management

## Installing XR Management

To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@latest/index.html).
To install this package, follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Manual/Packages.html).

This package is `com.unity.xr.management`

Expand Down
Loading

0 comments on commit 4bed75a

Please sign in to comment.