// // MaxIntegrationManager.cs // AppLovin MAX Unity Plugin // // Created by Santosh Bagadi on 6/1/19. // Copyright © 2019 AppLovin. All rights reserved. // using System; using System.Collections; using System.IO; using AppLovinMax.Internal; using UnityEditor; using UnityEngine; using UnityEngine.Networking; namespace AppLovinMax.Scripts.IntegrationManager.Editor { [Serializable] public class PluginData { // ReSharper disable InconsistentNaming - Consistent with JSON data. public Network AppLovinMax; public Network[] MediatedNetworks; public Network[] PartnerMicroSdks; public DynamicLibraryToEmbed[] ThirdPartyDynamicLibrariesToEmbed; public Alert[] Alerts; } [Serializable] public class Network { // // Sample network data: // // { // "Name": "adcolony", // "DisplayName": "AdColony", // "DownloadUrl": "https://bintray.com/applovin/Unity-Mediation-Packages/download_file?file_path=AppLovin-AdColony-Adapters-Android-3.3.10.1-iOS-3.3.7.2.unitypackage", // "PluginFileName": "AppLovin-AdColony-Adapters-Android-3.3.10.1-iOS-3.3.7.2.unitypackage", // "DependenciesFilePath": "MaxSdk/Mediation/AdColony/Editor/Dependencies.xml", // "LatestVersions" : { // "Unity": "android_3.3.10.1_ios_3.3.7.2", // "Android": "3.3.10.1", // "Ios": "3.3.7.2" // } // } // // ReSharper disable InconsistentNaming - Consistent with JSON data. public string Name; public string DisplayName; public string DownloadUrl; public string DependenciesFilePath; public PackageInfo[] Packages; public string[] PluginFilePaths; public Versions LatestVersions; public DynamicLibraryToEmbed[] DynamicLibrariesToEmbed; [NonSerialized] public Versions CurrentVersions; [NonSerialized] public MaxSdkUtils.VersionComparisonResult CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Lesser; [NonSerialized] public bool RequiresUpdate; [NonSerialized] public bool IsCurrentlyInstalling; } [Serializable] public class DynamicLibraryToEmbed { // ReSharper disable InconsistentNaming - Consistent with JSON data. public string PodName; public string[] FrameworkNames; // Min and max versions are inclusive, so if the adapter is the min or max version, the xcframework will get embedded. public string MinVersion; public string MaxVersion; public DynamicLibraryToEmbed(string podName, string[] frameworkNames, string minVersion, string maxVersion) { PodName = podName; FrameworkNames = frameworkNames; MinVersion = minVersion; MaxVersion = maxVersion; } } public enum Severity { Info, Warning, Error } [Serializable] public class Alert { public string SeverityType; public string Title; public string Message; public string Url; public string MinimumPluginVersion; public string MaximumPluginVersion; public string MinimumUnityVersion; public string MaximumUnityVersion; public Severity Severity; public void InitializeSeverityEnum() { switch (SeverityType) { case "INFO": Severity = Severity.Info; break; case "WARNING": Severity = Severity.Warning; break; case "ERROR": Severity = Severity.Error; break; default: MaxSdkLogger.E(string.Format("Alert <{0}> has unsupported severity type <{1}>.", Title, SeverityType)); Severity = Severity.Info; break; } } public bool ShouldShowAlert() { var pluginVersionValid = MaxSdkUtils.IsVersionInRange(MaxSdk.Version, MinimumPluginVersion, MaximumPluginVersion); var unityVersionValid = MaxSdkUtils.IsVersionInRange(Application.unityVersion, MinimumUnityVersion, MaximumUnityVersion); return pluginVersionValid && unityVersionValid; } } /// /// A helper data class used to get current versions from Dependency.xml files. /// [Serializable] public class Versions { // ReSharper disable InconsistentNaming - Consistent with JSON data. public string Unity; public string Android; public string Ios; public override bool Equals(object value) { var versions = value as Versions; return versions != null && Unity.Equals(versions.Unity) && (Android == null || Android.Equals(versions.Android)) && (Ios == null || Ios.Equals(versions.Ios)); } public bool HasEqualSdkVersions(Versions versions) { return versions != null && AdapterSdkVersion(Android).Equals(AdapterSdkVersion(versions.Android)) && AdapterSdkVersion(Ios).Equals(AdapterSdkVersion(versions.Ios)); } public override int GetHashCode() { return new {unity = Unity, android = Android, ios = Ios}.GetHashCode(); } private static string AdapterSdkVersion(string adapterVersion) { if (string.IsNullOrEmpty(adapterVersion)) return ""; var index = adapterVersion.LastIndexOf(".", StringComparison.Ordinal); return index > 0 ? adapterVersion.Substring(0, index) : adapterVersion; } } /// /// A manager class for MAX integration manager window. /// public class AppLovinIntegrationManager { /// /// Delegate to be called when a plugin package's import is started. /// internal delegate void ImportPackageStartedCallback(Network network); /// /// Delegate to be called when a plugin package is finished importing. /// /// The network data for which the package is imported. internal delegate void ImportPackageCompletedCallback(Network network); private static readonly AppLovinIntegrationManager instance = new AppLovinIntegrationManager(); internal static readonly string GradleTemplatePath = Path.Combine("Assets/Plugins/Android", "mainTemplate.gradle"); private const string MaxSdkAssetExportPath = "MaxSdk/Scripts/MaxSdk.cs"; private const string MaxSdkMediationExportPath = "MaxSdk/Mediation"; private const string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}"; private static string externalDependencyManagerVersion; internal static ImportPackageStartedCallback OnImportPackageStartedCallback; internal static ImportPackageCompletedCallback OnImportPackageCompletedCallback; private MaxWebRequest maxWebRequest; private Network importingNetwork; /// /// An Instance of the Integration manager. /// public static AppLovinIntegrationManager Instance { get { return instance; } } /// /// The parent directory path where the MaxSdk plugin directory is placed. /// public static string PluginParentDirectory { get { // Search for the asset with the export path label. // Paths are normalized using AltDirectorySeparatorChar (/) to ensure compatibility across platforms (in case of migrating a project from Windows to Mac or vice versa). var maxSdkScriptAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkAssetExportPath); // maxSdkScriptAssetPath will always have AltDirectorySeparatorChar (/) as the path separator. Convert to platform specific path. return maxSdkScriptAssetPath.Replace(MaxSdkAssetExportPath, "") .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } } public static string MediationDirectory { get { var mediationAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkMediationExportPath); return mediationAssetPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } } /// /// Whether or not the plugin is in the Unity Package Manager. /// public static bool IsPluginInPackageManager { get { return PluginParentDirectory.StartsWith("Packages"); } } /// /// Whether or not gradle build system is enabled. /// public static bool GradleBuildEnabled { get { return GetEditorUserBuildSetting("androidBuildSystem", "").ToString().Equals("Gradle"); } } /// /// Whether or not Gradle template is enabled. /// public static bool GradleTemplateEnabled { get { return GradleBuildEnabled && File.Exists(GradleTemplatePath); } } /// /// Whether or not the Quality Service settings can be processed which requires Gradle template enabled or Unity IDE newer than version 2018_2. /// public static bool CanProcessAndroidQualityServiceSettings { get { return GradleTemplateEnabled || GradleBuildEnabled; } } /// /// The External Dependency Manager version obtained dynamically. /// public static string ExternalDependencyManagerVersion { get { if (MaxSdkUtils.IsValidString(externalDependencyManagerVersion)) return externalDependencyManagerVersion; try { var versionHandlerVersionNumberType = Type.GetType("Google.VersionHandlerVersionNumber, Google.VersionHandlerImpl"); externalDependencyManagerVersion = versionHandlerVersionNumberType.GetProperty("Value").GetValue(null, null).ToString(); } #pragma warning disable 0168 catch (Exception ignored) #pragma warning restore 0168 { externalDependencyManagerVersion = "Failed to get version."; } return externalDependencyManagerVersion; } } private AppLovinIntegrationManager() { AssetDatabase.importPackageStarted += packageName => { if (!IsImportingNetwork(packageName)) return; CallImportPackageStartedCallback(importingNetwork); }; // Add asset import callbacks. AssetDatabase.importPackageCompleted += packageName => { if (!IsImportingNetwork(packageName)) return; AssetDatabase.Refresh(); CallImportPackageCompletedCallback(importingNetwork); importingNetwork = null; }; AssetDatabase.importPackageCancelled += packageName => { if (!IsImportingNetwork(packageName)) return; importingNetwork = null; }; AssetDatabase.importPackageFailed += (packageName, errorMessage) => { if (!IsImportingNetwork(packageName)) return; MaxSdkLogger.UserError(errorMessage); importingNetwork = null; }; } static AppLovinIntegrationManager() { } public static PluginData LoadPluginDataSync() { var url = string.Format(PluginDataEndpoint, MaxSdk.Version); var webRequestConfig = new WebRequestConfig() { EndPoint = url, }; var maxWebRequest = new MaxWebRequest(webRequestConfig); var webResponse = maxWebRequest.SendSync(); return CreatePluginDataFromWebResponse(webResponse); } /// /// Loads the plugin data to be display by integration manager window. /// /// Callback to be called once the plugin data download completes. public IEnumerator LoadPluginData(Action callback) { var url = string.Format(PluginDataEndpoint, MaxSdk.Version); var webRequestConfig = new WebRequestConfig() { EndPoint = url, }; maxWebRequest = new MaxWebRequest(webRequestConfig); yield return maxWebRequest.Send(webResponse => { var pluginData = CreatePluginDataFromWebResponse(webResponse); callback(pluginData); }); } private static PluginData CreatePluginDataFromWebResponse(WebResponse webResponse) { if (!webResponse.IsSuccess) { MaxSdkLogger.E("Failed to load plugin data. Please check your internet connection."); return null; } PluginData pluginData; try { pluginData = JsonUtility.FromJson(webResponse.ResponseMessage); AppLovinPackageManager.PluginData = pluginData; } catch (Exception exception) { Console.WriteLine(exception); pluginData = null; } if (pluginData == null) return null; // Get current version of the plugin var appLovinMax = pluginData.AppLovinMax; AppLovinPackageManager.UpdateCurrentVersions(appLovinMax); // Get current versions for all the mediation networks. foreach (var network in pluginData.MediatedNetworks) { AppLovinPackageManager.UpdateCurrentVersions(network); } if (pluginData.PartnerMicroSdks != null) { foreach (var partnerMicroSdk in pluginData.PartnerMicroSdks) { AppLovinPackageManager.UpdateCurrentVersions(partnerMicroSdk); } } if (pluginData.Alerts == null) return pluginData; // Initiate Severity enums from the raw strings in the response foreach (var alert in pluginData.Alerts) { alert.InitializeSeverityEnum(); } return pluginData; } /// /// Downloads the plugin file for a given network. /// /// Network for which to download the current version. /// Whether or not to show the import window when downloading. Defaults to true. /// public IEnumerator DownloadPlugin(Network network, bool showImport = true) { var path = Path.Combine(Application.temporaryCachePath, GetPluginFileName(network)); // TODO: Maybe delete plugin file after finishing import. var webRequestConfig = new WebRequestConfig() { DownloadHandler = new DownloadHandlerFile(path), EndPoint = network.DownloadUrl }; maxWebRequest = new MaxWebRequest(webRequestConfig); yield return maxWebRequest.Send(webResponse => { if (webResponse.IsSuccess) { importingNetwork = network; AssetDatabase.ImportPackage(path, showImport); } else { MaxSdkLogger.UserError("Failed to download plugin package: " + webResponse.ErrorMessage); } }); } /// /// Cancels the plugin download if one is in progress. /// public void CancelDownload() { if (maxWebRequest == null) return; maxWebRequest.Abort(); } /// /// Shows a dialog to the user with the given message and logs the error message to console. /// /// The failure message to be shown to the user. public static void ShowBuildFailureDialog(string message) { var openIntegrationManager = EditorUtility.DisplayDialog("AppLovin MAX", message, "Open Integration Manager", "Dismiss"); if (openIntegrationManager) { AppLovinIntegrationManagerWindow.ShowManager(); } MaxSdkLogger.UserError(message); } #region Utility Methods /// /// Checks whether or not the given package name is the currently importing package. /// /// The name of the package that needs to be checked. /// true if the importing package matches the given package name. private bool IsImportingNetwork(string packageName) { // Note: The pluginName doesn't have the '.unitypackage' extension included in its name but the pluginFileName does. So using Contains instead of Equals. return importingNetwork != null && GetPluginFileName(importingNetwork).Contains(packageName); } private static void CallImportPackageStartedCallback(Network network) { if (OnImportPackageStartedCallback == null) return; OnImportPackageStartedCallback(network); } private static void CallImportPackageCompletedCallback(Network network) { if (OnImportPackageCompletedCallback == null) return; OnImportPackageCompletedCallback(network); } private static object GetEditorUserBuildSetting(string name, object defaultValue) { var editorUserBuildSettingsType = typeof(EditorUserBuildSettings); var property = editorUserBuildSettingsType.GetProperty(name); if (property != null) { var value = property.GetValue(null, null); if (value != null) return value; } return defaultValue; } private static string GetPluginFileName(Network network) { return network.Name.ToLowerInvariant() + "_" + network.LatestVersions.Unity + ".unitypackage"; } #endregion } }