474 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			474 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
| //
 | |
| //  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 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;
 | |
|     }
 | |
| 
 | |
|     [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;
 | |
|     }
 | |
| 
 | |
|     [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;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// A helper data class used to get current versions from Dependency.xml files.
 | |
|     /// </summary>
 | |
|     [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;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// <summary>
 | |
|     /// A manager class for MAX integration manager window.
 | |
|     /// </summary>
 | |
|     public class AppLovinIntegrationManager
 | |
|     {
 | |
|         /// <summary>
 | |
|         /// Delegate to be called when downloading a plugin with the progress percentage. 
 | |
|         /// </summary>
 | |
|         /// <param name="pluginName">The name of the plugin being downloaded.</param>
 | |
|         /// <param name="progress">Percentage downloaded.</param>
 | |
|         /// <param name="done">Whether or not the download is complete.</param>
 | |
|         public delegate void DownloadPluginProgressCallback(string pluginName, float progress, bool done);
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Delegate to be called when a plugin package is imported.
 | |
|         /// </summary>
 | |
|         /// <param name="network">The network data for which the package is imported.</param>
 | |
|         public 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 static readonly string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}";
 | |
| 
 | |
|         private static string _externalDependencyManagerVersion;
 | |
| 
 | |
|         public static DownloadPluginProgressCallback OnDownloadPluginProgressCallback;
 | |
|         public static ImportPackageCompletedCallback OnImportPackageCompletedCallback;
 | |
| 
 | |
|         private UnityWebRequest webRequest;
 | |
|         private Network importingNetwork;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// An Instance of the Integration manager.
 | |
|         /// </summary>
 | |
|         public static AppLovinIntegrationManager Instance
 | |
|         {
 | |
|             get { return instance; }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The parent directory path where the MaxSdk plugin directory is placed.
 | |
|         /// </summary>
 | |
|         public static string PluginParentDirectory
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 // Search for the asset with the default exported path first, In most cases, we should be able to find the asset.
 | |
|                 // In some cases where we don't, use the platform specific export path to search for the asset (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);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether or not the plugin is in the Unity Package Manager.
 | |
|         /// </summary>
 | |
|         public static bool IsPluginInPackageManager
 | |
|         {
 | |
|             get { return PluginParentDirectory.StartsWith("Packages"); }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether or not gradle build system is enabled.
 | |
|         /// </summary>
 | |
|         public static bool GradleBuildEnabled
 | |
|         {
 | |
|             get { return GetEditorUserBuildSetting("androidBuildSystem", "").ToString().Equals("Gradle"); }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether or not Gradle template is enabled.
 | |
|         /// </summary>
 | |
|         public static bool GradleTemplateEnabled
 | |
|         {
 | |
|             get { return GradleBuildEnabled && File.Exists(GradleTemplatePath); }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether or not the Quality Service settings can be processed which requires Gradle template enabled or Unity IDE newer than version 2018_2.
 | |
|         /// </summary>
 | |
|         public static bool CanProcessAndroidQualityServiceSettings
 | |
|         {
 | |
|             get { return GradleTemplateEnabled || GradleBuildEnabled; }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The External Dependency Manager version obtained dynamically.
 | |
|         /// </summary>
 | |
|         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()
 | |
|         {
 | |
|             // Add asset import callbacks.
 | |
|             AssetDatabase.importPackageCompleted += packageName =>
 | |
|             {
 | |
|                 if (!IsImportingNetwork(packageName)) return;
 | |
| 
 | |
|                 AssetDatabase.Refresh();
 | |
| 
 | |
|                 CallImportPackageCompletedCallback(importingNetwork);
 | |
|                 importingNetwork = null;
 | |
|             };
 | |
| 
 | |
|             AssetDatabase.importPackageCancelled += packageName =>
 | |
|             {
 | |
|                 if (!IsImportingNetwork(packageName)) return;
 | |
| 
 | |
|                 MaxSdkLogger.UserDebug("Package import cancelled.");
 | |
|                 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);
 | |
|             using (var unityWebRequest = UnityWebRequest.Get(url))
 | |
|             {
 | |
|                 var operation = unityWebRequest.SendWebRequest();
 | |
| 
 | |
|                 // Just wait till www is done
 | |
|                 while (!operation.isDone) { }
 | |
| 
 | |
|                 return CreatePluginDataFromWebResponse(unityWebRequest);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Loads the plugin data to be display by integration manager window.
 | |
|         /// </summary>
 | |
|         /// <param name="callback">Callback to be called once the plugin data download completes.</param>
 | |
|         public IEnumerator LoadPluginData(Action<PluginData> callback)
 | |
|         {
 | |
|             var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
 | |
|             using (var unityWebRequest = UnityWebRequest.Get(url))
 | |
|             {
 | |
|                 var operation = unityWebRequest.SendWebRequest();
 | |
| 
 | |
|                 while (!operation.isDone) yield return new WaitForSeconds(0.1f); // Just wait till www is done. Our coroutine is pretty rudimentary.
 | |
| 
 | |
|                 var pluginData = CreatePluginDataFromWebResponse(unityWebRequest);
 | |
| 
 | |
|                 callback(pluginData);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static PluginData CreatePluginDataFromWebResponse(UnityWebRequest unityWebRequest)
 | |
|         {
 | |
| #if UNITY_2020_1_OR_NEWER
 | |
|             if (unityWebRequest.result != UnityWebRequest.Result.Success)
 | |
| #else
 | |
|             if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
 | |
| #endif
 | |
|             {
 | |
|                 MaxSdkLogger.E("Failed to load plugin data. Please check your internet connection.");
 | |
|                 return null;
 | |
|             }
 | |
| 
 | |
|             PluginData pluginData;
 | |
|             try
 | |
|             {
 | |
|                 pluginData = JsonUtility.FromJson<PluginData>(unityWebRequest.downloadHandler.text);
 | |
|                 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);
 | |
|             }
 | |
| 
 | |
|             foreach (var partnerMicroSdk in pluginData.PartnerMicroSdks)
 | |
|             {
 | |
|                 AppLovinPackageManager.UpdateCurrentVersions(partnerMicroSdk);
 | |
|             }
 | |
| 
 | |
|             return pluginData;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Downloads the plugin file for a given network.
 | |
|         /// </summary>
 | |
|         /// <param name="network">Network for which to download the current version.</param>
 | |
|         /// <param name="showImport">Whether or not to show the import window when downloading. Defaults to <c>true</c>.</param>
 | |
|         /// <returns></returns>
 | |
|         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 downloadHandler = new DownloadHandlerFile(path);
 | |
|             webRequest = new UnityWebRequest(network.DownloadUrl)
 | |
|             {
 | |
|                 method = UnityWebRequest.kHttpVerbGET,
 | |
|                 downloadHandler = downloadHandler
 | |
|             };
 | |
| 
 | |
|             var operation = webRequest.SendWebRequest();
 | |
|             while (!operation.isDone)
 | |
|             {
 | |
|                 yield return new WaitForSeconds(0.1f); // Just wait till webRequest is completed. Our coroutine is pretty rudimentary.
 | |
|                 CallDownloadPluginProgressCallback(network.DisplayName, operation.progress, operation.isDone);
 | |
|             }
 | |
| 
 | |
| #if UNITY_2020_1_OR_NEWER
 | |
|             if (webRequest.result != UnityWebRequest.Result.Success)
 | |
| #else
 | |
|             if (webRequest.isNetworkError || webRequest.isHttpError)
 | |
| #endif
 | |
|             {
 | |
|                 MaxSdkLogger.UserError(webRequest.error);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 importingNetwork = network;
 | |
|                 AssetDatabase.ImportPackage(path, showImport);
 | |
|             }
 | |
| 
 | |
|             webRequest.Dispose();
 | |
|             webRequest = null;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Cancels the plugin download if one is in progress.
 | |
|         /// </summary>
 | |
|         public void CancelDownload()
 | |
|         {
 | |
|             if (webRequest == null) return;
 | |
| 
 | |
|             webRequest.Abort();
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Shows a dialog to the user with the given message and logs the error message to console.
 | |
|         /// </summary>
 | |
|         /// <param name="message">The failure message to be shown to the user.</param>
 | |
|         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
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Checks whether or not the given package name is the currently importing package.
 | |
|         /// </summary>
 | |
|         /// <param name="packageName">The name of the package that needs to be checked.</param>
 | |
|         /// <returns>true if the importing package matches the given package name.</returns>
 | |
|         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 CallDownloadPluginProgressCallback(string pluginName, float progress, bool isDone)
 | |
|         {
 | |
|             if (OnDownloadPluginProgressCallback == null) return;
 | |
| 
 | |
|             OnDownloadPluginProgressCallback(pluginName, progress, isDone);
 | |
|         }
 | |
| 
 | |
|         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
 | |
|     }
 | |
| }
 |