#if ENABLE_CLOUD_SERVICES_ANALYTICS || UNITY_2023_2_OR_NEWER
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.XR.CoreUtils.Editor;
using UnityEngine.Rendering;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Inputs.Simulation;
using UnityEditor.XR.Interaction.Toolkit.ProjectValidation;
#if XR_MANAGEMENT_4_0_OR_NEWER
using UnityEditor.XR.Management;
using UnityEngine.XR.Management;
#endif
#if OCULUS_3_0_OR_NEWER
using Unity.XR.Oculus;
#endif
#if OPENXR_1_6_OR_NEWER
using UnityEditor.XR.OpenXR.Features;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#endif
namespace UnityEditor.XR.Interaction.Toolkit.Analytics.Hooks
{
///
/// Utility class for collecting data for XRI analytics, such as Project Settings for XR Plug-in Management and OpenXR.
/// Some data is included in both the build and play mode analytics payloads, so this class contains common code
/// for getting data for either.
///
static class XRIAnalyticsUtility
{
const string k_PackageDisplayName = "XR Interaction Toolkit";
const string k_TeleportLayerName = "Teleport";
const int k_TeleportLayerIndex = 31;
public struct XRManagementData
{
public bool initManagerOnStart;
public string activeLoader;
public string[] activeLoaders;
public bool isOculusEnabled;
public bool isOpenXREnabled;
}
static PackageVersionData[] s_PackageVersions;
///
/// These are the list of relevant XR packages we want to report in the analytics payload.
/// We gather the installed version of these packages to report in the payload.
///
static readonly string[] s_PackagesToCheck =
{
// Unity XR packages
"com.unity.xr.androidxr-openxr",
"com.unity.xr.arfoundation",
"com.unity.xr.hands",
"com.unity.xr.management",
"com.unity.xr.meta-openxr",
"com.unity.xr.oculus",
"com.unity.xr.openxr",
"com.unity.xr.visionos",
// Google packages
"com.google.xr.extensions",
// Meta packages
"com.meta.xr.sdk.all",
"com.meta.xr.mrutilitykit",
"com.meta.xr.sdk.audio",
"com.meta.xr.sdk.avatars",
"com.meta.xr.sdk.core",
"com.meta.xr.sdk.haptics",
"com.meta.xr.sdk.interaction",
"com.meta.xr.sdk.interaction.ovr",
"com.meta.xr.sdk.platform",
"com.meta.xr.sdk.simulator",
"com.meta.xr.simulator",
"com.meta.xr.sdk.voice",
// Microsoft MRTK packages
"com.microsoft.mixedreality.toolkit.foundation",
"com.microsoft.mixedreality.toolkit.standardassets",
"com.microsoft.mixedreality.toolkit.extensions",
"com.microsoft.mixedreality.toolkit.tools",
"com.microsoft.mixedreality.toolkit.handphysicsservice",
"org.mixedrealitytoolkit.core",
"org.mixedrealitytoolkit.accessibility",
"org.mixedrealitytoolkit.audio",
"org.mixedrealitytoolkit.environment",
"org.mixedrealitytoolkit.input",
"org.mixedrealitytoolkit.spatialmanipulation",
"org.mixedrealitytoolkit.tools",
"org.mixedrealitytoolkit.standardassets",
"org.mixedrealitytoolkit.uxcore",
"org.mixedrealitytoolkit.windowsspeech",
"org.mixedrealitytoolkit.uxcomponents",
"org.mixedrealitytoolkit.uxcomponents.noncanvas",
};
public static XRManagementData GetXRManagementDataPlayMode()
{
#if XR_MANAGEMENT_4_0_OR_NEWER
// Editor Play mode uses Desktop Platform Settings regardless of Active Build Target.
var generalSettings = XRGeneralSettings.Instance;
return GetXRManagementData(generalSettings);
#else
return default;
#endif
}
public static XRManagementData GetXRManagementDataBuild()
{
var activeBuildTarget = EditorUserBuildSettings.activeBuildTarget;
var activeBuildTargetGroup = BuildPipeline.GetBuildTargetGroup(activeBuildTarget);
return GetXRManagementDataBuild(activeBuildTargetGroup);
}
public static XRManagementData GetXRManagementDataBuild(BuildTargetGroup buildTargetGroup)
{
#if XR_MANAGEMENT_4_0_OR_NEWER
var generalSettings = XRGeneralSettingsPerBuildTarget.XRGeneralSettingsForBuildTarget(buildTargetGroup);
return GetXRManagementData(generalSettings);
#else
return default;
#endif
}
#if XR_MANAGEMENT_4_0_OR_NEWER
static XRManagementData GetXRManagementData(XRGeneralSettings generalSettings)
{
var data = new XRManagementData();
if (generalSettings != null)
{
data.initManagerOnStart = generalSettings.InitManagerOnStart;
var managerSettings = generalSettings.Manager;
if (managerSettings != null)
{
data.activeLoader = managerSettings.activeLoader != null ? managerSettings.activeLoader.GetType().Name : null;
data.activeLoaders = managerSettings.activeLoaders.Where(loader => loader != null).Select(loader => loader.GetType().Name).ToArray();
#if OCULUS_3_0_OR_NEWER
data.isOculusEnabled = managerSettings.activeLoaders.Where(loader => loader != null).Any(loader => loader is OculusLoader);
#endif
#if OPENXR_1_6_OR_NEWER
data.isOpenXREnabled = managerSettings.activeLoaders.Where(loader => loader != null).Any(loader => loader is OpenXRLoader);
#endif
}
}
return data;
}
#endif
public static OculusProjectSettingsData GetOculusSettingsData()
{
var data = new OculusProjectSettingsData();
#if OCULUS_3_0_OR_NEWER
var attr = typeof(OculusSettings).GetCustomAttribute();
var settingsKey = attr != null ? attr.buildSettingsKey : "Unity.XR.Oculus.Settings";
if (EditorBuildSettings.TryGetConfigObject(settingsKey, out var oculusSettings))
{
data.valid = true;
data.renderModeDesktop = (int)oculusSettings.m_StereoRenderingModeDesktop;
data.renderModeAndroid = (int)oculusSettings.m_StereoRenderingModeAndroid;
#if OCULUS_3_2_OR_NEWER
data.foveationMethod = (int)oculusSettings.FoveatedRenderingMethod;
#else
data.foveationMethod = -1;
#endif
}
#endif
return data;
}
public static OpenXRProjectSettingsData GetOpenXRSettingsData(BuildTargetGroup buildTargetGroup)
{
var data = new OpenXRProjectSettingsData();
#if OPENXR_1_6_OR_NEWER
var openXRSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
if (openXRSettings != null)
{
var unityInteractionFeatures = new List();
var unityFeatures = new List();
var customInteractionFeaturesCount = 0;
var customFeaturesCount = 0;
foreach (var feature in openXRSettings.GetFeatures())
{
if (feature == null || !feature.enabled)
continue;
var type = feature.GetType();
var attr = type.GetCustomAttribute();
if (attr == null)
continue;
var unityType = attr.Company == "Unity" || IsUnityAssembly(type);
if (feature is OpenXRInteractionFeature)
{
if (unityType)
unityInteractionFeatures.Add(attr.FeatureId);
else
customInteractionFeaturesCount++;
}
else
{
if (unityType)
unityFeatures.Add(attr.FeatureId);
else
customFeaturesCount++;
}
}
data.valid = true;
data.renderMode = (int)openXRSettings.renderMode;
data.unityInteractionFeaturesCount = unityInteractionFeatures.Count;
data.unityFeaturesCount = unityFeatures.Count;
data.customInteractionFeaturesCount = customInteractionFeaturesCount;
data.customFeaturesCount = customFeaturesCount;
data.unityInteractionFeatures = unityInteractionFeatures.OrderBy(id => id).ToArray();
data.unityFeatures = unityFeatures.OrderBy(id => id).ToArray();
}
#endif
return data;
}
public static PackageVersionData[] GetPackageVersionData()
{
if (s_PackageVersions == null)
{
var foundPackages = new List(s_PackagesToCheck.Length);
foreach (var package in PackageManager.PackageInfo.GetAllRegisteredPackages())
{
if (s_PackagesToCheck.Contains(package.name))
foundPackages.Add(new PackageVersionData { package = package.name, version = package.version });
}
s_PackageVersions = foundPackages.ToArray();
}
return s_PackageVersions;
}
public static SampleVersionData[] GetImportedXRISamplesData()
{
var samples = new List<(string sampleName, PackageVersion packageVersion)>();
ProjectValidationUtility.GetImportedSamples(k_PackageDisplayName, samples);
return samples.OrderBy(sample => sample.sampleName).Select(sample => new SampleVersionData { sample = sample.sampleName, version = sample.packageVersion.ToString(), }).ToArray();
}
public static GeneralProjectSettingsData GetGeneralProjectSettingsData()
{
return new GeneralProjectSettingsData
{
activeInputHandling = GetActiveInputHandlingMode() ?? -1,
hasRenderPipeline = GraphicsSettings.currentRenderPipeline != null,
};
}
public static XRIProjectSettingsData GetXRIProjectSettingsData()
{
var xriProjectSettings = new XRIProjectSettingsData();
// The static Instance getter in these settings classes can cause assets to be created on disk,
// so use GetInstanceOrLoadOnly to avoid creating the settings assets if they don't already exist.
var simulatorSettings = XRDeviceSimulatorSettings.GetInstanceOrLoadOnly();
if (simulatorSettings != null)
{
xriProjectSettings.automaticallyInstantiateSimulatorPrefab = simulatorSettings.automaticallyInstantiateSimulatorPrefab;
xriProjectSettings.automaticallyInstantiateInEditorOnly = simulatorSettings.automaticallyInstantiateInEditorOnly;
xriProjectSettings.hasSimulatorPrefab = simulatorSettings.simulatorPrefab != null;
}
var interactionEditorSettings = XRInteractionEditorSettings.GetInstanceOrLoadOnly();
if (interactionEditorSettings != null)
{
xriProjectSettings.inputReaderPropertyDrawerMode = (int)interactionEditorSettings.inputReaderPropertyDrawerMode;
}
var interactionLayerSettings = InteractionLayerSettings.GetInstanceOrLoadOnly();
if (interactionLayerSettings != null)
{
var userLayersCount = 0;
for (var i = InteractionLayerSettings.builtInLayerSize; i < InteractionLayerSettings.layerSize; ++i)
{
if (!interactionLayerSettings.IsLayerEmpty(i))
userLayersCount++;
}
xriProjectSettings.userLayersCount = userLayersCount;
xriProjectSettings.hasTeleportLayer31 = string.Equals(interactionLayerSettings.GetLayerNameAt(k_TeleportLayerIndex), k_TeleportLayerName, StringComparison.OrdinalIgnoreCase);
}
return xriProjectSettings;
}
static int? GetActiveInputHandlingMode()
{
var playerSettings = (SerializedObject)typeof(PlayerSettings).GetMethod("GetSerializedObject", BindingFlags.NonPublic | BindingFlags.Static)?.Invoke(null, null);
var activeInputHandlerProp = playerSettings?.FindProperty("activeInputHandler");
return activeInputHandlerProp?.intValue;
}
public static bool IsUnityAssembly(Type type)
{
var assemblyName = type.Assembly.GetName().Name;
return assemblyName.StartsWith("Unity.") || assemblyName.StartsWith("UnityEngine.");
}
}
}
#endif