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);
}
}
}
}
}