using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using AppLovinMax.ThirdParty.MiniJson; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif public class MaxSdkUtils { /// /// An Enum to be used when comparing two versions. /// /// If: /// A < B return /// A == B return /// A > B return /// public enum VersionComparisonResult { Lesser = -1, Equal = 0, Greater = 1 } #if UNITY_ANDROID && !UNITY_EDITOR private static readonly AndroidJavaClass MaxUnityPluginClass = new AndroidJavaClass("com.applovin.mediation.unity.MaxUnityPlugin"); #endif #if UNITY_IOS [DllImport("__Internal")] private static extern float _MaxGetAdaptiveBannerHeight(float width); #endif /// /// Get the adaptive banner size for the provided width. /// If the width is not provided, will assume full screen width for the current orientation. /// /// NOTE: Only AdMob / Google Ad Manager currently has support for adaptive banners and the maximum height is 15% the height of the screen. /// /// /// The width to retrieve the adaptive banner height for. /// The adaptive banner height for the current orientation and width. public static float GetAdaptiveBannerHeight(float width = -1.0f) { #if UNITY_EDITOR return 50.0f; #elif UNITY_IOS return _MaxGetAdaptiveBannerHeight(width); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("getAdaptiveBannerHeight", width); #else return -1.0f; #endif } /// /// Tries to get a dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the dictionary /// The key to be used to retrieve the dictionary /// The default value to be returned when a value for the given key is not found. /// The dictionary for the given key if available, the default value otherwise. public static Dictionary GetDictionaryFromDictionary(IDictionary dictionary, string key, Dictionary defaultValue = null) { if (dictionary == null) return defaultValue; object value; if (dictionary.TryGetValue(key, out value) && value is Dictionary) { return value as Dictionary; } return defaultValue; } /// /// Tries to get a list from the dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the list /// The key to be used to retrieve the list /// The default value to be returned when a value for the given key is not found. /// The list for the given key if available, the default value otherwise. public static List GetListFromDictionary(IDictionary dictionary, string key, List defaultValue = null) { if (dictionary == null) return defaultValue; object value; if (dictionary.TryGetValue(key, out value) && value is List) { return value as List; } return defaultValue; } /// /// Tries to get a string value from dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the string value. /// The key to be used to retrieve the string value. /// The default value to be returned when a value for the given key is not found. /// The string value from the dictionary if available, the default value otherwise. public static string GetStringFromDictionary(IDictionary dictionary, string key, string defaultValue = "") { if (dictionary == null) return defaultValue; object value; if (dictionary.TryGetValue(key, out value) && value != null) { return value.ToString(); } return defaultValue; } /// /// Tries to get a bool value from dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the bool value. /// The key to be used to retrieve the bool value. /// The default value to be returned when a bool value for the given key is not found. /// The bool value from the dictionary if available, the default value otherwise. public static bool GetBoolFromDictionary(IDictionary dictionary, string key, bool defaultValue = false) { if (dictionary == null) return defaultValue; object obj; bool value; if (dictionary.TryGetValue(key, out obj) && obj != null && bool.TryParse(obj.ToString(), out value)) { return value; } return defaultValue; } /// /// Tries to get a int value from dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the int value. /// The key to be used to retrieve the int value. /// The default value to be returned when a int value for the given key is not found. /// The int value from the dictionary if available, the default value otherwise. public static int GetIntFromDictionary(IDictionary dictionary, string key, int defaultValue = 0) { if (dictionary == null) return defaultValue; object obj; int value; if (dictionary.TryGetValue(key, out obj) && obj != null && int.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value)) { return value; } return defaultValue; } /// /// Tries to get a long value from dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the long value. /// The key to be used to retrieve the long value. /// The default value to be returned when a long value for the given key is not found. /// The long value from the dictionary if available, the default value otherwise. public static long GetLongFromDictionary(IDictionary dictionary, string key, long defaultValue = 0L) { if (dictionary == null) return defaultValue; object obj; long value; if (dictionary.TryGetValue(key, out obj) && obj != null && long.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value)) { return value; } return defaultValue; } /// /// Tries to get a float value from dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the float value. /// The key to be used to retrieve the float value. /// The default value to be returned when a string value for the given key is not found. /// The float value from the dictionary if available, the default value otherwise. public static float GetFloatFromDictionary(IDictionary dictionary, string key, float defaultValue = 0F) { if (dictionary == null) return defaultValue; object obj; float value; if (dictionary.TryGetValue(key, out obj) && obj != null && float.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value)) { return value; } return defaultValue; } /// /// Tries to get a double value from dictionary for the given key if available, returns the default value if unavailable. /// /// The dictionary from which to get the double value. /// The key to be used to retrieve the double value. /// The default value to be returned when a double value for the given key is not found. /// The double value from the dictionary if available, the default value otherwise. public static double GetDoubleFromDictionary(IDictionary dictionary, string key, int defaultValue = 0) { if (dictionary == null) return defaultValue; object obj; double value; if (dictionary.TryGetValue(key, out obj) && obj != null && double.TryParse(InvariantCultureToString(obj), NumberStyles.Any, CultureInfo.InvariantCulture, out value)) { return value; } return defaultValue; } /// /// Converts the given object to a string without locale specific conversions. /// public static string InvariantCultureToString(object obj) { return string.Format(CultureInfo.InvariantCulture, "{0}", obj); } /// /// The native iOS and Android plugins forward JSON arrays of JSON Objects. /// public static List PropsStringsToList(string str) { var result = new List(); if (string.IsNullOrEmpty(str)) return result; var infoArray = Json.Deserialize(str) as List; if (infoArray == null) return result; foreach (var infoObject in infoArray) { var dictionary = infoObject as Dictionary; if (dictionary == null) continue; // Dynamically construct generic type with string argument. // The type T must have a constructor that creates a new object from an info string, i.e., new T(infoString) var instance = (T) Activator.CreateInstance(typeof(T), dictionary); result.Add(instance); } return result; } /// /// Returns the hexidecimal color code string for the given Color. /// public static String ParseColor(Color color) { int a = (int) (Mathf.Clamp01(color.a) * Byte.MaxValue); int r = (int) (Mathf.Clamp01(color.r) * Byte.MaxValue); int g = (int) (Mathf.Clamp01(color.g) * Byte.MaxValue); int b = (int) (Mathf.Clamp01(color.b) * Byte.MaxValue); return BitConverter.ToString(new[] { Convert.ToByte(a), Convert.ToByte(r), Convert.ToByte(g), Convert.ToByte(b), }).Replace("-", "").Insert(0, "#"); } #if UNITY_IOS [DllImport("__Internal")] private static extern bool _MaxIsTablet(); #endif /// /// Returns whether or not the device is a tablet. /// public static bool IsTablet() { #if UNITY_EDITOR return false; #elif UNITY_IOS return _MaxIsTablet(); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("isTablet"); #else return false; #endif } #if UNITY_IOS [DllImport("__Internal")] private static extern bool _MaxIsPhysicalDevice(); #endif /// /// Returns whether or not a physical device is being used, as opposed to an emulator / simulator. /// public static bool IsPhysicalDevice() { #if UNITY_EDITOR return false; #elif UNITY_IOS return _MaxIsPhysicalDevice(); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("isPhysicalDevice"); #else return false; #endif } #if UNITY_IOS [DllImport("__Internal")] private static extern float _MaxScreenDensity(); #endif /// /// Returns the screen density. /// public static float GetScreenDensity() { #if UNITY_EDITOR return 1; #elif UNITY_IOS return _MaxScreenDensity(); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("getScreenDensity"); #else return -1; #endif } /// /// Parses the IABTCF_VendorConsents string to determine the consent status of the IAB vendor with the provided ID. /// NOTE: Must be called after AppLovin MAX SDK has been initialized. /// /// Vendor ID as defined in the Global Vendor List. /// true if the vendor has consent, false if not, or null if TC data is not available on disk. /// Current Version of Global Vendor List public static bool? GetTcfConsentStatus(int vendorId) { var tcfConsentStatus = GetPlatformSpecificTcfConsentStatus(vendorId); return GetConsentStatusValue(tcfConsentStatus); } #if UNITY_IOS [DllImport("__Internal")] private static extern int _MaxGetTcfVendorConsentStatus(int vendorIdentifier); #endif private static int GetPlatformSpecificTcfConsentStatus(int vendorId) { #if UNITY_EDITOR return -1; #elif UNITY_IOS return _MaxGetTcfVendorConsentStatus(vendorId); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("getTcfVendorConsentStatus", vendorId); #else return -1; #endif } /// /// Parses the IABTCF_AddtlConsent string to determine the consent status of the advertising entity with the provided Ad Technology Provider (ATP) ID. /// NOTE: Must be called after AppLovin MAX SDK has been initialized. /// /// ATP ID of the advertising entity (e.g. 89 for Meta Audience Network). /// /// true if the advertising entity has consent, false if not, or null if no AC string is available on disk or the ATP network was not listed in the CMP flow. /// /// Google’s Additional Consent Mode technical specification /// List of Google ATPs and their IDs public static bool? GetAdditionalConsentStatus(int atpId) { var additionalConsentStatus = GetPlatformSpecificAdditionalConsentStatus(atpId); return GetConsentStatusValue(additionalConsentStatus); } #if UNITY_IOS [DllImport("__Internal")] private static extern int _MaxGetAdditionalConsentStatus(int atpIdentifier); #endif private static int GetPlatformSpecificAdditionalConsentStatus(int atpId) { #if UNITY_EDITOR return -1; #elif UNITY_IOS return _MaxGetAdditionalConsentStatus(atpId); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("getAdditionalConsentStatus", atpId); #else return -1; #endif } /// /// Parses the IABTCF_PurposeConsents String to determine the consent status of the IAB defined data processing purpose. /// NOTE: Must be called after AppLovin MAX SDK has been initialized. /// /// Purpose ID. /// true if the purpose has consent, false if not, or null if TC data is not available on disk. /// see IAB Europe Transparency and Consent Framework Policies (Appendix A) for purpose definitions. public static bool? GetPurposeConsentStatus(int purposeId) { var purposeConsentStatus = GetPlatformSpecificPurposeConsentStatus(purposeId); return GetConsentStatusValue(purposeConsentStatus); } #if UNITY_IOS [DllImport("__Internal")] private static extern int _MaxGetPurposeConsentStatus(int purposeIdentifier); #endif private static int GetPlatformSpecificPurposeConsentStatus(int purposeId) { #if UNITY_EDITOR return -1; #elif UNITY_IOS return _MaxGetPurposeConsentStatus(purposeId); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("getPurposeConsentStatus", purposeId); #else return -1; #endif } /// /// Parses the IABTCF_SpecialFeaturesOptIns String to determine the opt-in status of the IAB defined special feature. /// NOTE: Must be called after AppLovin MAX SDK has been initialized. /// /// Special feature ID. /// true if the user opted in for the special feature, false if not, or null if TC data is not available on disk. /// IAB Europe Transparency and Consent Framework Policies (Appendix A) for special features public static bool? GetSpecialFeatureOptInStatus(int specialFeatureId) { var specialFeatureOptInStatus = GetPlatformSpecificSpecialFeatureOptInStatus(specialFeatureId); return GetConsentStatusValue(specialFeatureOptInStatus); } #if UNITY_IOS [DllImport("__Internal")] private static extern int _MaxGetSpecialFeatureOptInStatus(int specialFeatureIdentifier); #endif private static int GetPlatformSpecificSpecialFeatureOptInStatus(int specialFeatureId) { #if UNITY_EDITOR return -1; #elif UNITY_IOS return _MaxGetSpecialFeatureOptInStatus(specialFeatureId); #elif UNITY_ANDROID return MaxUnityPluginClass.CallStatic("getSpecialFeatureOptInStatus", specialFeatureId); #else return -1; #endif } private static bool? GetConsentStatusValue(int consentStatus) { if (consentStatus == -1) { return null; } else { return consentStatus == 1; } } /// /// Compares AppLovin MAX Unity mediation adapter plugin versions. Returns , , /// or as the first version is less than, equal to, or greater than the second. /// /// If a version for a specific platform is only present in one of the provided versions, the one that contains it is considered newer. /// /// The first version to be compared. /// The second version to be compared. /// /// if versionA is less than versionB. /// if versionA and versionB are equal. /// if versionA is greater than versionB. /// public static VersionComparisonResult CompareUnityMediationVersions(string versionA, string versionB) { if (versionA.Equals(versionB)) return VersionComparisonResult.Equal; // Unity version would be of format: android_w.x.y.z_ios_a.b.c.d // For Android only versions it would be: android_w.x.y.z // For iOS only version it would be: ios_a.b.c.d // After splitting into their respective components, the versions would be at the odd indices. var versionAComponents = versionA.Split('_').ToList(); var versionBComponents = versionB.Split('_').ToList(); var androidComparison = VersionComparisonResult.Equal; if (versionA.Contains("android") && versionB.Contains("android")) { var androidVersionA = versionAComponents[1]; var androidVersionB = versionBComponents[1]; androidComparison = CompareVersions(androidVersionA, androidVersionB); // Remove the Android version component so that iOS versions can be processed. versionAComponents.RemoveRange(0, 2); versionBComponents.RemoveRange(0, 2); } else if (versionA.Contains("android")) { androidComparison = VersionComparisonResult.Greater; // Remove the Android version component so that iOS versions can be processed. versionAComponents.RemoveRange(0, 2); } else if (versionB.Contains("android")) { androidComparison = VersionComparisonResult.Lesser; // Remove the Android version component so that iOS version can be processed. versionBComponents.RemoveRange(0, 2); } var iosComparison = VersionComparisonResult.Equal; if (versionA.Contains("ios") && versionB.Contains("ios")) { var iosVersionA = versionAComponents[1]; var iosVersionB = versionBComponents[1]; iosComparison = CompareVersions(iosVersionA, iosVersionB); } else if (versionA.Contains("ios")) { iosComparison = VersionComparisonResult.Greater; } else if (versionB.Contains("ios")) { iosComparison = VersionComparisonResult.Lesser; } // If either one of the Android or iOS version is greater, the entire version should be greater. return (androidComparison == VersionComparisonResult.Greater || iosComparison == VersionComparisonResult.Greater) ? VersionComparisonResult.Greater : VersionComparisonResult.Lesser; } /// /// Compares its two arguments for order. Returns , , /// or as the first version is less than, equal to, or greater than the second. /// /// The first version to be compared. /// The second version to be compared. /// /// if versionA is less than versionB. /// if versionA and versionB are equal. /// if versionA is greater than versionB. /// public static VersionComparisonResult CompareVersions(string versionA, string versionB) { if (versionA.Equals(versionB)) return VersionComparisonResult.Equal; // Check if either of the versions are beta versions. Beta versions could be of format x.y.z-beta or x.y.z-betaX. // Split the version string into beta component and the underlying version. int piece; var isVersionABeta = versionA.Contains("-beta"); var versionABetaNumber = 0; if (isVersionABeta) { var components = versionA.Split(new[] { "-beta" }, StringSplitOptions.None); versionA = components[0]; versionABetaNumber = int.TryParse(components[1], out piece) ? piece : 0; } var isVersionBBeta = versionB.Contains("-beta"); var versionBBetaNumber = 0; if (isVersionBBeta) { var components = versionB.Split(new[] { "-beta" }, StringSplitOptions.None); versionB = components[0]; versionBBetaNumber = int.TryParse(components[1], out piece) ? piece : 0; } // Now that we have separated the beta component, check if the underlying versions are the same. if (versionA.Equals(versionB)) { // The versions are the same, compare the beta components. if (isVersionABeta && isVersionBBeta) { if (versionABetaNumber < versionBBetaNumber) return VersionComparisonResult.Lesser; if (versionABetaNumber > versionBBetaNumber) return VersionComparisonResult.Greater; } // Only VersionA is beta, so A is older. else if (isVersionABeta) { return VersionComparisonResult.Lesser; } // Only VersionB is beta, A is newer. else { return VersionComparisonResult.Greater; } } // Compare the non beta component of the version string. var versionAComponents = versionA.Split('.').Select(version => int.TryParse(version, out piece) ? piece : 0).ToArray(); var versionBComponents = versionB.Split('.').Select(version => int.TryParse(version, out piece) ? piece : 0).ToArray(); var length = Mathf.Max(versionAComponents.Length, versionBComponents.Length); for (var i = 0; i < length; i++) { var aComponent = i < versionAComponents.Length ? versionAComponents[i] : 0; var bComponent = i < versionBComponents.Length ? versionBComponents[i] : 0; if (aComponent < bComponent) return VersionComparisonResult.Lesser; if (aComponent > bComponent) return VersionComparisonResult.Greater; } return VersionComparisonResult.Equal; } /// /// Check if the given string is valid - not null and not empty. /// /// The string to be checked. /// true if the given string is not null and not empty. public static bool IsValidString(string toCheck) { return !string.IsNullOrEmpty(toCheck); } #if UNITY_EDITOR /// /// Gets the path of the asset in the project for a given MAX plugin export path. /// /// The actual exported path of the asset. /// The exported path of the MAX plugin asset or the default export path if the asset is not found. public static string GetAssetPathForExportPath(string exportPath) { var defaultPath = Path.Combine("Assets", exportPath); var assetLabelToFind = "l:al_max_export_path-" + exportPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var assetGuids = AssetDatabase.FindAssets(assetLabelToFind); return assetGuids.Length < 1 ? defaultPath : AssetDatabase.GUIDToAssetPath(assetGuids[0]); } #endif }