#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