You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/MaxSdk/Scripts/MaxSdkUtils.cs

663 lines
27 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
{
/// <summary>
/// An Enum to be used when comparing two versions.
///
/// If:
/// A &lt; B return <see cref="Lesser"/>
/// A == B return <see cref="Equal"/>
/// A &gt; B return <see cref="Greater"/>
/// </summary>
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
/// <summary>
/// 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.
/// </summary>
///
/// <param name="width">The width to retrieve the adaptive banner height for.</param>
/// <returns>The adaptive banner height for the current orientation and width.</returns>
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<float>("getAdaptiveBannerHeight", width);
#else
return -1.0f;
#endif
}
/// <summary>
/// Tries to get a dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the dictionary</param>
/// <param name="key">The key to be used to retrieve the dictionary</param>
/// <param name="defaultValue">The default value to be returned when a value for the given key is not found.</param>
/// <returns>The dictionary for the given key if available, the default value otherwise.</returns>
public static Dictionary<string, object> GetDictionaryFromDictionary(IDictionary<string, object> dictionary, string key, Dictionary<string, object> defaultValue = null)
{
if (dictionary == null) return defaultValue;
object value;
if (dictionary.TryGetValue(key, out value) && value is Dictionary<string, object>)
{
return value as Dictionary<string, object>;
}
return defaultValue;
}
/// <summary>
/// Tries to get a list from the dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the list</param>
/// <param name="key">The key to be used to retrieve the list</param>
/// <param name="defaultValue">The default value to be returned when a value for the given key is not found.</param>
/// <returns>The list for the given key if available, the default value otherwise.</returns>
public static List<object> GetListFromDictionary(IDictionary<string, object> dictionary, string key, List<object> defaultValue = null)
{
if (dictionary == null) return defaultValue;
object value;
if (dictionary.TryGetValue(key, out value) && value is List<object>)
{
return value as List<object>;
}
return defaultValue;
}
/// <summary>
/// Tries to get a <c>string</c> value from dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the <c>string</c> value.</param>
/// <param name="key">The key to be used to retrieve the <c>string</c> value.</param>
/// <param name="defaultValue">The default value to be returned when a value for the given key is not found.</param>
/// <returns>The <c>string</c> value from the dictionary if available, the default value otherwise.</returns>
public static string GetStringFromDictionary(IDictionary<string, object> 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;
}
/// <summary>
/// Tries to get a <c>bool</c> value from dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the <c>bool</c> value.</param>
/// <param name="key">The key to be used to retrieve the <c>bool</c> value.</param>
/// <param name="defaultValue">The default value to be returned when a <c>bool</c> value for the given key is not found.</param>
/// <returns>The <c>bool</c> value from the dictionary if available, the default value otherwise.</returns>
public static bool GetBoolFromDictionary(IDictionary<string, object> 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;
}
/// <summary>
/// Tries to get a <c>int</c> value from dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the <c>int</c> value.</param>
/// <param name="key">The key to be used to retrieve the <c>int</c> value.</param>
/// <param name="defaultValue">The default value to be returned when a <c>int</c> value for the given key is not found.</param>
/// <returns>The <c>int</c> value from the dictionary if available, the default value otherwise.</returns>
public static int GetIntFromDictionary(IDictionary<string, object> 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;
}
/// <summary>
/// Tries to get a <c>long</c> value from dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the <c>long</c> value.</param>
/// <param name="key">The key to be used to retrieve the <c>long</c> value.</param>
/// <param name="defaultValue">The default value to be returned when a <c>long</c> value for the given key is not found.</param>
/// <returns>The <c>long</c> value from the dictionary if available, the default value otherwise.</returns>
public static long GetLongFromDictionary(IDictionary<string, object> 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;
}
/// <summary>
/// Tries to get a <c>float</c> value from dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the <c>float</c> value.</param>
/// <param name="key">The key to be used to retrieve the <c>float</c> value.</param>
/// <param name="defaultValue">The default value to be returned when a <c>string</c> value for the given key is not found.</param>
/// <returns>The <c>float</c> value from the dictionary if available, the default value otherwise.</returns>
public static float GetFloatFromDictionary(IDictionary<string, object> 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;
}
/// <summary>
/// Tries to get a <c>double</c> value from dictionary for the given key if available, returns the default value if unavailable.
/// </summary>
/// <param name="dictionary">The dictionary from which to get the <c>double</c> value.</param>
/// <param name="key">The key to be used to retrieve the <c>double</c> value.</param>
/// <param name="defaultValue">The default value to be returned when a <c>double</c> value for the given key is not found.</param>
/// <returns>The <c>double</c> value from the dictionary if available, the default value otherwise.</returns>
public static double GetDoubleFromDictionary(IDictionary<string, object> 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;
}
/// <summary>
/// Converts the given object to a string without locale specific conversions.
/// </summary>
public static string InvariantCultureToString(object obj)
{
return string.Format(CultureInfo.InvariantCulture, "{0}", obj);
}
/// <summary>
/// The native iOS and Android plugins forward JSON arrays of JSON Objects.
/// </summary>
public static List<T> PropsStringsToList<T>(string str)
{
var result = new List<T>();
if (string.IsNullOrEmpty(str)) return result;
var infoArray = Json.Deserialize(str) as List<object>;
if (infoArray == null) return result;
foreach (var infoObject in infoArray)
{
var dictionary = infoObject as Dictionary<string, object>;
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;
}
/// <summary>
/// Returns the hexidecimal color code string for the given Color.
/// </summary>
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
/// <summary>
/// Returns whether or not the device is a tablet.
/// </summary>
public static bool IsTablet()
{
#if UNITY_EDITOR
return false;
#elif UNITY_IOS
return _MaxIsTablet();
#elif UNITY_ANDROID
return MaxUnityPluginClass.CallStatic<bool>("isTablet");
#else
return false;
#endif
}
#if UNITY_IOS
[DllImport("__Internal")]
private static extern bool _MaxIsPhysicalDevice();
#endif
/// <summary>
/// Returns whether or not a physical device is being used, as opposed to an emulator / simulator.
/// </summary>
public static bool IsPhysicalDevice()
{
#if UNITY_EDITOR
return false;
#elif UNITY_IOS
return _MaxIsPhysicalDevice();
#elif UNITY_ANDROID
return MaxUnityPluginClass.CallStatic<bool>("isPhysicalDevice");
#else
return false;
#endif
}
#if UNITY_IOS
[DllImport("__Internal")]
private static extern float _MaxScreenDensity();
#endif
/// <summary>
/// Returns the screen density.
/// </summary>
public static float GetScreenDensity()
{
#if UNITY_EDITOR
return 1;
#elif UNITY_IOS
return _MaxScreenDensity();
#elif UNITY_ANDROID
return MaxUnityPluginClass.CallStatic<float>("getScreenDensity");
#else
return -1;
#endif
}
/// <summary>
/// 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.
/// </summary>
/// <param name="vendorId">Vendor ID as defined in the Global Vendor List.</param>
/// <returns><c>true</c> if the vendor has consent, <c>false</c> if not, or <c>null</c> if TC data is not available on disk.</returns>
/// <see href="https://vendor-list.consensu.org/v3/vendor-list.json">Current Version of Global Vendor List</see>
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<int>("getTcfVendorConsentStatus", vendorId);
#else
return -1;
#endif
}
/// <summary>
/// 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.
/// </summary>
/// <param name="atpId">ATP ID of the advertising entity (e.g. 89 for Meta Audience Network).</param>
/// <returns>
/// <c>true</c> if the advertising entity has consent, <c>false</c> if not, or <c>null</c> if no AC string is available on disk or the ATP network was not listed in the CMP flow.
/// </returns>
/// <see href="https://support.google.com/admanager/answer/9681920">Googles Additional Consent Mode technical specification</see>
/// <see href="https://storage.googleapis.com/tcfac/additional-consent-providers.csv">List of Google ATPs and their IDs</see>
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<int>("getAdditionalConsentStatus", atpId);
#else
return -1;
#endif
}
/// <summary>
/// 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.
/// </summary>
/// <param name="purposeId">Purpose ID.</param>
/// <returns><c>true</c> if the purpose has consent, <c>false</c> if not, or <c>null</c> if TC data is not available on disk.</returns>
/// <see href="https://storage.googleapis.com/tcfac/additional-consent-providers.csv">see IAB Europe Transparency and Consent Framework Policies (Appendix A) for purpose definitions.</see>
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<int>("getPurposeConsentStatus", purposeId);
#else
return -1;
#endif
}
/// <summary>
/// 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.
/// </summary>
/// <param name="specialFeatureId">Special feature ID.</param>
/// <returns><c>true</c> if the user opted in for the special feature, <c>false</c> if not, or <c>null</c> if TC data is not available on disk.</returns>
/// <see href="https://iabeurope.eu/iab-europe-transparency-consent-framework-policies">IAB Europe Transparency and Consent Framework Policies (Appendix A) for special features </see>
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<int>("getSpecialFeatureOptInStatus", specialFeatureId);
#else
return -1;
#endif
}
private static bool? GetConsentStatusValue(int consentStatus)
{
if (consentStatus == -1)
{
return null;
}
else
{
return consentStatus == 1;
}
}
/// <summary>
/// Compares AppLovin MAX Unity mediation adapter plugin versions. Returns <see cref="VersionComparisonResult.Lesser"/>, <see cref="VersionComparisonResult.Equal"/>,
/// or <see cref="VersionComparisonResult.Greater"/> 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.
/// </summary>
/// <param name="versionA">The first version to be compared.</param>
/// <param name="versionB">The second version to be compared.</param>
/// <returns>
/// <see cref="VersionComparisonResult.Lesser"/> if versionA is less than versionB.
/// <see cref="VersionComparisonResult.Equal"/> if versionA and versionB are equal.
/// <see cref="VersionComparisonResult.Greater"/> if versionA is greater than versionB.
/// </returns>
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;
}
/// <summary>
/// Compares its two arguments for order. Returns <see cref="VersionComparisonResult.Lesser"/>, <see cref="VersionComparisonResult.Equal"/>,
/// or <see cref="VersionComparisonResult.Greater"/> as the first version is less than, equal to, or greater than the second.
/// </summary>
/// <param name="versionA">The first version to be compared.</param>
/// <param name="versionB">The second version to be compared.</param>
/// <returns>
/// <see cref="VersionComparisonResult.Lesser"/> if versionA is less than versionB.
/// <see cref="VersionComparisonResult.Equal"/> if versionA and versionB are equal.
/// <see cref="VersionComparisonResult.Greater"/> if versionA is greater than versionB.
/// </returns>
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;
}
/// <summary>
/// Check if the given string is valid - not <c>null</c> and not empty.
/// </summary>
/// <param name="toCheck">The string to be checked.</param>
/// <returns><c>true</c> if the given string is not <c>null</c> and not empty.</returns>
public static bool IsValidString(string toCheck)
{
return !string.IsNullOrEmpty(toCheck);
}
#if UNITY_EDITOR
/// <summary>
/// Gets the path of the asset in the project for a given MAX plugin export path.
/// </summary>
/// <param name="exportPath">The actual exported path of the asset.</param>
/// <returns>The exported path of the MAX plugin asset or the default export path if the asset is not found.</returns>
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
}