Files
Bachelor-Arbeit-Thomas-Wichert/Assets/Oculus/VR/Editor/OVRManifestPreprocessor.cs
2025-07-21 09:11:14 +02:00

707 lines
28 KiB
C#

/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Xml;
using Oculus.VR.Editor;
public class OVRManifestPreprocessor
{
private static readonly string ManifestFileName = "AndroidManifest.xml";
private static readonly string ManifestFolderName = "Plugins/Android";
private static readonly string ManifestFolderPathAbsolute =
Path.Combine(Application.dataPath, ManifestFolderName);
private static readonly string BuildManifestFolderPathRelative =
Path.Combine("Assets", ManifestFolderName);
private static readonly string BuildManifestFilePathAbsolute =
Path.Combine(ManifestFolderPathAbsolute, ManifestFileName);
private static readonly string BuildManifestFilePathRelative =
Path.Combine(BuildManifestFolderPathRelative, ManifestFileName);
[MenuItem("Oculus/Tools/Create store-compatible AndroidManifest.xml", false, 100000)]
public static void GenerateManifestForSubmission()
{
var so = ScriptableObject.CreateInstance(typeof(OVRPluginInfo));
var script = MonoScript.FromScriptableObject(so);
string assetPath = AssetDatabase.GetAssetPath(script);
string editorDir = Directory.GetParent(assetPath).FullName;
string srcFile = editorDir + "/AndroidManifest.OVRSubmission.xml";
if (!File.Exists(srcFile))
{
Debug.LogError("Cannot find Android manifest template for submission." +
" Please delete the OVR folder and reimport the Oculus Utilities.");
return;
}
if (DoesAndroidManifestExist())
{
if (!EditorUtility.DisplayDialog("AndroidManifest.xml Already Exists!",
"Would you like to replace the existing manifest with a new one? All modifications will be lost.",
"Replace", "Cancel"))
{
return;
}
}
// IO methods use absolute paths
if (!Directory.Exists(ManifestFolderPathAbsolute))
Directory.CreateDirectory(ManifestFolderPathAbsolute);
PatchAndroidManifest(srcFile, BuildManifestFilePathAbsolute, false);
AssetDatabase.Refresh();
}
public static bool DoesAndroidManifestExist()
{
// IO methods use absolute paths
return File.Exists(BuildManifestFilePathAbsolute);
}
[MenuItem("Oculus/Tools/Update AndroidManifest.xml")]
public static void UpdateAndroidManifest()
{
if (!DoesAndroidManifestExist())
{
Debug.LogError(
"Unable to update manifest because it does not exist! Run \"Create store-compatible AndroidManifest.xml\" first");
return;
}
if (!EditorUtility.DisplayDialog("Update AndroidManifest.xml",
"This will overwrite all Oculus specific AndroidManifest Settings. Continue?", "Overwrite", "Cancel"))
{
return;
}
PatchAndroidManifest(BuildManifestFilePathAbsolute, skipExistingAttributes: false);
AssetDatabase.Refresh();
}
[MenuItem("Oculus/Tools/Remove AndroidManifest.xml")]
public static void RemoveAndroidManifest()
{
// AssetDatabase functions uses relative paths
AssetDatabase.DeleteAsset(BuildManifestFilePathRelative);
AssetDatabase.Refresh();
}
private static void AddOrRemoveTag(XmlDocument doc, string @namespace, string path, string elementName, string name,
bool required, bool modifyIfFound, params string[] attrs) // name, value pairs
{
var nodes = doc.SelectNodes(path + "/" + elementName);
XmlElement element = null;
foreach (XmlElement e in nodes)
{
if (name == null || name == e.GetAttribute("name", @namespace))
{
element = e;
break;
}
}
if (required)
{
if (element == null)
{
var parent = doc.SelectSingleNode(path);
element = doc.CreateElement(elementName);
element.SetAttribute("name", @namespace, name);
parent.AppendChild(element);
}
for (int i = 0; i < attrs.Length; i += 2)
{
if (modifyIfFound || string.IsNullOrEmpty(element.GetAttribute(attrs[i], @namespace)))
{
if (attrs[i + 1] != null)
{
element.SetAttribute(attrs[i], @namespace, attrs[i + 1]);
}
else
{
element.RemoveAttribute(attrs[i], @namespace);
}
}
}
}
else
{
if (element != null && modifyIfFound)
{
element.ParentNode.RemoveChild(element);
}
}
}
private static void AddReplaceValueTag(XmlDocument doc, string @namespace, string path, string elementName, string name)
{
XmlElement element = (XmlElement)doc.SelectSingleNode("/manifest");
if (element == null)
{
UnityEngine.Debug.LogError("Could not find manifest tag in android manifest.");
return;
}
string toolsNamespace = element.GetAttribute("xmlns:tools");
var nodes = doc.SelectNodes(path + "/" + elementName);
foreach (XmlElement e in nodes)
{
if (name == null || name == e.GetAttribute("name", @namespace))
{
element = e;
break;
}
}
element.SetAttribute("replace", toolsNamespace, "android:value");
}
public static void PatchAndroidManifest(string sourceFile, string destinationFile = null,
bool skipExistingAttributes = true, bool enableSecurity = false)
{
if (destinationFile == null)
{
destinationFile = sourceFile;
}
bool modifyIfFound = !skipExistingAttributes;
try
{
// Load android manfiest file
XmlDocument doc = new XmlDocument();
doc.Load(sourceFile);
string androidNamespaceURI;
XmlElement element = (XmlElement)doc.SelectSingleNode("/manifest");
if (element == null)
{
UnityEngine.Debug.LogError("Could not find manifest tag in android manifest.");
return;
}
// Get android namespace URI from the manifest
androidNamespaceURI = element.GetAttribute("xmlns:android");
if (string.IsNullOrEmpty(androidNamespaceURI))
{
UnityEngine.Debug.LogError("Could not find Android Namespace in manifest.");
return;
}
ApplyRequiredManfiestTags(doc, androidNamespaceURI, modifyIfFound, enableSecurity);
ApplyFeatureManfiestTags(doc, androidNamespaceURI, modifyIfFound);
// The following manifest entries are all handled through Oculus XR SDK Plugin
#if !PRIORITIZE_OCULUS_XR_SETTINGS
ApplyOculusXRManifestTags(doc, androidNamespaceURI, modifyIfFound);
ApplyTargetDevicesManifestTags(doc, androidNamespaceURI, true /*modifyIfFound*/);
#endif
doc.Save(destinationFile);
}
catch (System.Exception e)
{
UnityEngine.Debug.LogException(e);
}
}
private static void ApplyRequiredManfiestTags(XmlDocument doc, string androidNamespaceURI, bool modifyIfFound,
bool enableSecurity)
{
OVRProjectConfig projectConfig = OVRProjectConfig.GetProjectConfig();
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application/activity/intent-filter",
"category",
"android.intent.category.LEANBACK_LAUNCHER",
required: false,
modifyIfFound: true); // always remove leanback launcher
// First add or remove headtracking flag if targeting Quest
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"android.hardware.vr.headtracking",
OVRDeviceSelector.isTargetDeviceQuestFamily,
true,
"version", "1",
"required", OVRProjectConfig.GetProjectConfig().allowOptional3DofHeadTracking ? "false" : "true");
// make sure android label and icon are set in the manifest
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"application",
null,
true,
modifyIfFound,
"label", "@string/app_name",
"icon", "@mipmap/app_icon",
// Disable allowBackup in manifest and add Android NSC XML file
"allowBackup", projectConfig.disableBackups ? "false" : "true",
"networkSecurityConfig", projectConfig.enableNSCConfig && enableSecurity ? "@xml/network_sec_config" : null
);
}
private static void ApplyFeatureManfiestTags(XmlDocument doc, string androidNamespaceURI, bool modifyIfFound)
{
OVRProjectConfig projectConfig = OVRProjectConfig.GetProjectConfig();
OVRRuntimeSettings runtimeSettings = OVRRuntimeSettings.GetRuntimeSettings();
//============================================================================
// Hand Tracking
// If Quest is the target device, add the handtracking manifest tags if needed
// Mapping of project setting to manifest setting:
// OVRProjectConfig.HandTrackingSupport.ControllersOnly => manifest entry not present
// OVRProjectConfig.HandTrackingSupport.ControllersAndHands => manifest entry present and required=false
// OVRProjectConfig.HandTrackingSupport.HandsOnly => manifest entry present and required=true
OVRProjectConfig.HandTrackingSupport targetHandTrackingSupport =
OVRProjectConfig.GetProjectConfig().handTrackingSupport;
OVRProjectConfig.HandTrackingVersion targetHandTrackingVersion =
OVRProjectConfig.GetProjectConfig().handTrackingVersion;
bool handTrackingEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(targetHandTrackingSupport !=
OVRProjectConfig.HandTrackingSupport.ControllersOnly);
bool handTrackingVersionEntryNeeded = handTrackingEntryNeeded &&
(targetHandTrackingVersion !=
OVRProjectConfig.HandTrackingVersion.Default);
string handTrackingVersionValue =
(targetHandTrackingVersion == OVRProjectConfig.HandTrackingVersion.V2) ? "V2.0" : "V1.0";
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"oculus.software.handtracking",
handTrackingEntryNeeded,
modifyIfFound,
"required",
(targetHandTrackingSupport == OVRProjectConfig.HandTrackingSupport.HandsOnly) ? "true" : "false");
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
"com.oculus.permission.HAND_TRACKING",
handTrackingEntryNeeded,
modifyIfFound);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.handtracking.frequency",
handTrackingEntryNeeded,
modifyIfFound,
"value", projectConfig.handTrackingFrequency.ToString());
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.handtracking.version",
handTrackingVersionEntryNeeded,
modifyIfFound,
"value", handTrackingVersionValue);
//============================================================================
// System Keyboard
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"oculus.software.overlay_keyboard",
projectConfig.requiresSystemKeyboard,
modifyIfFound,
"required", "false");
//============================================================================
// Experimental Features
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"com.oculus.experimental.enabled",
projectConfig.experimentalFeaturesEnabled,
modifyIfFound,
"required", "true");
//============================================================================
// Anchor
OVRProjectConfig.AnchorSupport targetAnchorSupport = OVRProjectConfig.GetProjectConfig().anchorSupport;
bool anchorEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(targetAnchorSupport == OVRProjectConfig.AnchorSupport.Enabled);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
"com.oculus.permission.USE_ANCHOR_API",
anchorEntryNeeded,
modifyIfFound);
var targetSharedAnchorSupport = OVRProjectConfig.GetProjectConfig().sharedAnchorSupport;
bool sharedAnchorEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
targetSharedAnchorSupport != OVRProjectConfig.FeatureSupport.None;
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
"com.oculus.permission.IMPORT_EXPORT_IOT_MAP_DATA",
sharedAnchorEntryNeeded,
modifyIfFound,
"required", targetSharedAnchorSupport.ToRequiredAttributeValue());
//============================================================================
// Passthrough
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"com.oculus.feature.PASSTHROUGH",
projectConfig.insightPassthroughSupport != OVRProjectConfig.FeatureSupport.None,
modifyIfFound,
"required", projectConfig.insightPassthroughSupport.ToRequiredAttributeValue());
//============================================================================
// System Splash Screen
if (projectConfig.systemSplashScreen != null)
{
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.ossplash",
true,
modifyIfFound,
"value",
"true");
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.ossplash.type",
true,
modifyIfFound,
"value",
projectConfig.systemSplashScreenType.ToManifestTag());
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.ossplash.colorspace",
true,
modifyIfFound,
"value",
ColorSpaceToManifestTag(runtimeSettings.colorSpace));
}
//============================================================================
// Render Model
OVRProjectConfig.RenderModelSupport renderModelSupport = OVRProjectConfig.GetProjectConfig().renderModelSupport;
bool renderModelEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(renderModelSupport == OVRProjectConfig.RenderModelSupport.Enabled);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"com.oculus.feature.RENDER_MODEL",
renderModelEntryNeeded,
modifyIfFound);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
"com.oculus.permission.RENDER_MODEL",
renderModelEntryNeeded,
modifyIfFound);
//============================================================================
// Tracked Keyboard
// If Quest is the target device, add the tracked keyboard manifest tags if needed
// Mapping of project setting to manifest setting:
// OVRProjectConfig.TrackedKeyboardSupport.None => manifest entry not present
// OVRProjectConfig.TrackedKeyboardSupport.Supported => manifest entry present and required=false
// OVRProjectConfig.TrackedKeyboardSupport.Required => manifest entry present and required=true
OVRProjectConfig.TrackedKeyboardSupport targetTrackedKeyboardSupport =
OVRProjectConfig.GetProjectConfig().trackedKeyboardSupport;
bool trackedKeyboardEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(targetTrackedKeyboardSupport !=
OVRProjectConfig.TrackedKeyboardSupport.None);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"oculus.software.trackedkeyboard",
trackedKeyboardEntryNeeded,
modifyIfFound,
"required",
(targetTrackedKeyboardSupport == OVRProjectConfig.TrackedKeyboardSupport.Required) ? "true" : "false");
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
"com.oculus.permission.TRACKED_KEYBOARD",
trackedKeyboardEntryNeeded,
modifyIfFound);
//============================================================================
// Body Tracking
// If Quest is the target device, add the bodytracking manifest tags if needed
var targetBodyTrackingSupport = OVRProjectConfig.GetProjectConfig().bodyTrackingSupport;
bool bodyTrackingEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(targetBodyTrackingSupport != OVRProjectConfig.FeatureSupport.None);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"com.oculus.software.body_tracking",
bodyTrackingEntryNeeded,
(targetBodyTrackingSupport == OVRProjectConfig.FeatureSupport.Required)
? true
: modifyIfFound, // If Required, we should override the current entry
"required", (targetBodyTrackingSupport == OVRProjectConfig.FeatureSupport.Required) ? "true" : "false");
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
OVRPermissionsRequester.GetPermissionId(OVRPermissionsRequester.Permission.BodyTracking),
bodyTrackingEntryNeeded,
modifyIfFound);
//============================================================================
// Face Tracking
var targetFaceTrackingSupport = OVRProjectConfig.GetProjectConfig().faceTrackingSupport;
bool faceTrackingEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(targetFaceTrackingSupport != OVRProjectConfig.FeatureSupport.None);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"oculus.software.face_tracking",
faceTrackingEntryNeeded,
(targetFaceTrackingSupport == OVRProjectConfig.FeatureSupport.Required)
? true
: modifyIfFound, // If Required, we should override the current entry
"required", (targetFaceTrackingSupport == OVRProjectConfig.FeatureSupport.Required) ? "true" : "false");
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
OVRPermissionsRequester.GetPermissionId(OVRPermissionsRequester.Permission.FaceTracking),
faceTrackingEntryNeeded,
modifyIfFound);
//============================================================================
// Eye Tracking
var targetEyeTrackingSupport = OVRProjectConfig.GetProjectConfig().eyeTrackingSupport;
bool eyeTrackingEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(targetEyeTrackingSupport != OVRProjectConfig.FeatureSupport.None);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"oculus.software.eye_tracking",
eyeTrackingEntryNeeded,
(targetEyeTrackingSupport == OVRProjectConfig.FeatureSupport.Required)
? true
: modifyIfFound, // If Required, we should override the current entry
"required", (targetEyeTrackingSupport == OVRProjectConfig.FeatureSupport.Required) ? "true" : "false");
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
OVRPermissionsRequester.GetPermissionId(OVRPermissionsRequester.Permission.EyeTracking),
eyeTrackingEntryNeeded,
modifyIfFound);
//============================================================================
// Virtual Keyboard
var virtualKeyboardSupport = OVRProjectConfig.GetProjectConfig().virtualKeyboardSupport;
bool virtualKeyboardEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(virtualKeyboardSupport != OVRProjectConfig.FeatureSupport.None);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-feature",
"com.oculus.feature.VIRTUAL_KEYBOARD",
virtualKeyboardEntryNeeded,
(virtualKeyboardSupport == OVRProjectConfig.FeatureSupport.Required)
? true
: modifyIfFound, // If Required, we should override the current entry
"required", (virtualKeyboardSupport == OVRProjectConfig.FeatureSupport.Required) ? "true" : "false");
//============================================================================
// Scene
var sceneSupport = OVRProjectConfig.GetProjectConfig().sceneSupport;
bool sceneEntryNeeded = OVRDeviceSelector.isTargetDeviceQuestFamily &&
(sceneSupport != OVRProjectConfig.FeatureSupport.None);
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest",
"uses-permission",
OVRPermissionsRequester.GetPermissionId(OVRPermissionsRequester.Permission.Scene),
sceneEntryNeeded,
modifyIfFound);
//============================================================================
// Processor Favor
var processorFavor = OVRProjectConfig.GetProjectConfig().processorFavor;
bool tradeCpuForGpuAmountNeeded = processorFavor != OVRProjectConfig.ProcessorFavor.FavorEqually;
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.trade_cpu_for_gpu_amount",
required: tradeCpuForGpuAmountNeeded,
modifyIfFound: true,
"value", ((int)processorFavor).ToString());
}
private static void ApplyOculusXRManifestTags(XmlDocument doc, string androidNamespaceURI, bool modifyIfFound)
{
// Add focus aware tag if this app is targeting Quest Family
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application/activity",
"meta-data",
"com.oculus.vr.focusaware",
OVRDeviceSelector.isTargetDeviceQuestFamily,
modifyIfFound,
"value", "true");
// Add VR intent filter tag in the manifest
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application/activity/intent-filter",
"category",
"com.oculus.intent.category.VR",
required: true,
modifyIfFound: true);
}
private static void ApplyTargetDevicesManifestTags(XmlDocument doc, string androidNamespaceURI, bool modifyIfFound)
{
// Add support devices manifest according to the target devices
if (OVRDeviceSelector.isTargetDeviceQuestFamily)
{
string targetDeviceValue = "";
if (OVRDeviceSelector.isTargetDeviceQuest)
{
if (string.IsNullOrEmpty(targetDeviceValue))
targetDeviceValue = "quest";
else
targetDeviceValue += "|quest";
}
if (OVRDeviceSelector.isTargetDeviceQuest2)
{
if (string.IsNullOrEmpty(targetDeviceValue))
targetDeviceValue = "quest2";
else
targetDeviceValue += "|quest2";
}
if (OVRDeviceSelector.isTargetDeviceQuestPro)
{
if (string.IsNullOrEmpty(targetDeviceValue))
targetDeviceValue = "questpro";
else
targetDeviceValue += "|questpro";
}
if (OVRDeviceSelector.isTargetDeviceQuest3)
{
if (string.IsNullOrEmpty(targetDeviceValue))
targetDeviceValue = "eureka";
else
targetDeviceValue += "|eureka";
}
if (string.IsNullOrEmpty(targetDeviceValue))
{
Debug.LogError("Empty target devices");
}
AddOrRemoveTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.supportedDevices",
true,
modifyIfFound,
"value", targetDeviceValue);
#if XR_MGMT_4_4_0_OR_NEWER && USING_XR_SDK_OPENXR
// Fixes a manifest merge edge case where the supported devices tag collides with a cached version when using new XR Management manifest system
AddReplaceValueTag(doc,
androidNamespaceURI,
"/manifest/application",
"meta-data",
"com.oculus.supportedDevices");
#endif
}
}
private static string ColorSpaceToManifestTag(OVRManager.ColorSpace colorSpace)
{
switch (colorSpace)
{
case OVRManager.ColorSpace.Unmanaged:
return "!Unmanaged";
case OVRManager.ColorSpace.Rec_2020:
return "Rec.2020";
case OVRManager.ColorSpace.Rec_709:
return "Rec.709";
case OVRManager.ColorSpace.Rift_CV1:
return "!RiftCV1";
case OVRManager.ColorSpace.Rift_S:
return "!RiftS";
case OVRManager.ColorSpace.Quest:
return "!Quest";
case OVRManager.ColorSpace.P3:
return "P3";
case OVRManager.ColorSpace.Adobe_RGB:
return "Adobe";
default:
return "";
}
}
}