2026-04-20 13:49:36 +08:00
//
// 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 ;
2026-05-08 11:03:00 +08:00
using AppLovinMax.Internal ;
2026-04-20 13:49:36 +08:00
using UnityEditor ;
using UnityEngine ;
using UnityEngine.Networking ;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
[Serializable]
public class PluginData
{
2026-05-08 11:03:00 +08:00
// ReSharper disable InconsistentNaming - Consistent with JSON data.
2026-04-20 13:49:36 +08:00
public Network AppLovinMax ;
public Network [ ] MediatedNetworks ;
public Network [ ] PartnerMicroSdks ;
public DynamicLibraryToEmbed [ ] ThirdPartyDynamicLibrariesToEmbed ;
2026-05-08 11:03:00 +08:00
public Alert [ ] Alerts ;
2026-04-20 13:49:36 +08:00
}
[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"
// }
// }
//
2026-05-08 11:03:00 +08:00
// ReSharper disable InconsistentNaming - Consistent with JSON data.
2026-04-20 13:49:36 +08:00
public string Name ;
public string DisplayName ;
public string DownloadUrl ;
public string DependenciesFilePath ;
public PackageInfo [ ] Packages ;
public string [ ] PluginFilePaths ;
public Versions LatestVersions ;
2026-05-08 11:03:00 +08:00
public DynamicLibraryToEmbed [ ] DynamicLibrariesToEmbed ;
2026-04-20 13:49:36 +08:00
[NonSerialized] public Versions CurrentVersions ;
[NonSerialized] public MaxSdkUtils . VersionComparisonResult CurrentToLatestVersionComparisonResult = MaxSdkUtils . VersionComparisonResult . Lesser ;
[NonSerialized] public bool RequiresUpdate ;
2026-05-08 11:03:00 +08:00
[NonSerialized] public bool IsCurrentlyInstalling ;
2026-04-20 13:49:36 +08:00
}
[Serializable]
public class DynamicLibraryToEmbed
{
2026-05-08 11:03:00 +08:00
// ReSharper disable InconsistentNaming - Consistent with JSON data.
2026-04-20 13:49:36 +08:00
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 ;
}
}
2026-05-08 11:03:00 +08:00
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 ;
}
}
2026-04-20 13:49:36 +08:00
/// <summary>
/// A helper data class used to get current versions from Dependency.xml files.
/// </summary>
[Serializable]
public class Versions
{
2026-05-08 11:03:00 +08:00
// ReSharper disable InconsistentNaming - Consistent with JSON data.
2026-04-20 13:49:36 +08:00
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 ( )
{
2026-05-08 11:03:00 +08:00
return new { unity = Unity , android = Android , ios = Ios } . GetHashCode ( ) ;
2026-04-20 13:49:36 +08:00
}
private static string AdapterSdkVersion ( string adapterVersion )
{
2026-05-08 11:03:00 +08:00
if ( string . IsNullOrEmpty ( adapterVersion ) ) return "" ;
var index = adapterVersion . LastIndexOf ( "." , StringComparison . Ordinal ) ;
2026-04-20 13:49:36 +08:00
return index > 0 ? adapterVersion . Substring ( 0 , index ) : adapterVersion ;
}
}
/// <summary>
/// A manager class for MAX integration manager window.
/// </summary>
public class AppLovinIntegrationManager
{
/// <summary>
2026-05-08 11:03:00 +08:00
/// Delegate to be called when a plugin package's import is started.
2026-04-20 13:49:36 +08:00
/// </summary>
2026-05-08 11:03:00 +08:00
internal delegate void ImportPackageStartedCallback ( Network network ) ;
2026-04-20 13:49:36 +08:00
/// <summary>
2026-05-08 11:03:00 +08:00
/// Delegate to be called when a plugin package is finished importing.
2026-04-20 13:49:36 +08:00
/// </summary>
/// <param name="network">The network data for which the package is imported.</param>
2026-05-08 11:03:00 +08:00
internal delegate void ImportPackageCompletedCallback ( Network network ) ;
2026-04-20 13:49:36 +08:00
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" ;
2026-05-08 11:03:00 +08:00
private const string MaxSdkMediationExportPath = "MaxSdk/Mediation" ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
private const string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}" ;
2026-04-20 13:49:36 +08:00
private static string externalDependencyManagerVersion ;
2026-05-08 11:03:00 +08:00
internal static ImportPackageStartedCallback OnImportPackageStartedCallback ;
internal static ImportPackageCompletedCallback OnImportPackageCompletedCallback ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
private MaxWebRequest maxWebRequest ;
2026-04-20 13:49:36 +08:00
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
{
2026-05-08 11:03:00 +08:00
// 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).
2026-04-20 13:49:36 +08:00
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 ) ;
}
}
2026-05-08 11:03:00 +08:00
public static string MediationDirectory
{
get
{
var mediationAssetPath = MaxSdkUtils . GetAssetPathForExportPath ( MaxSdkMediationExportPath ) ;
return mediationAssetPath . Replace ( Path . AltDirectorySeparatorChar , Path . DirectorySeparatorChar ) ;
}
}
2026-04-20 13:49:36 +08:00
/// <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
{
2026-05-08 11:03:00 +08:00
if ( MaxSdkUtils . IsValidString ( externalDependencyManagerVersion ) ) return externalDependencyManagerVersion ;
2026-04-20 13:49:36 +08:00
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 ( )
{
2026-05-08 11:03:00 +08:00
AssetDatabase . importPackageStarted + = packageName = >
{
if ( ! IsImportingNetwork ( packageName ) ) return ;
CallImportPackageStartedCallback ( importingNetwork ) ;
} ;
2026-04-20 13:49:36 +08:00
// 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 ) ;
2026-05-08 11:03:00 +08:00
var webRequestConfig = new WebRequestConfig ( )
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
EndPoint = url ,
} ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
var maxWebRequest = new MaxWebRequest ( webRequestConfig ) ;
var webResponse = maxWebRequest . SendSync ( ) ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
return CreatePluginDataFromWebResponse ( webResponse ) ;
2026-04-20 13:49:36 +08:00
}
/// <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 ) ;
2026-05-08 11:03:00 +08:00
var webRequestConfig = new WebRequestConfig ( )
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
EndPoint = url ,
} ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
maxWebRequest = new MaxWebRequest ( webRequestConfig ) ;
yield return maxWebRequest . Send ( webResponse = >
{
var pluginData = CreatePluginDataFromWebResponse ( webResponse ) ;
2026-04-20 13:49:36 +08:00
callback ( pluginData ) ;
2026-05-08 11:03:00 +08:00
} ) ;
2026-04-20 13:49:36 +08:00
}
2026-05-08 11:03:00 +08:00
private static PluginData CreatePluginDataFromWebResponse ( WebResponse webResponse )
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
if ( ! webResponse . IsSuccess )
2026-04-20 13:49:36 +08:00
{
MaxSdkLogger . E ( "Failed to load plugin data. Please check your internet connection." ) ;
return null ;
}
PluginData pluginData ;
try
{
2026-05-08 11:03:00 +08:00
pluginData = JsonUtility . FromJson < PluginData > ( webResponse . ResponseMessage ) ;
2026-04-20 13:49:36 +08:00
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 ) ;
}
2026-05-08 11:03:00 +08:00
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 )
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
alert . InitializeSeverityEnum ( ) ;
2026-04-20 13:49:36 +08:00
}
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.
2026-05-08 11:03:00 +08:00
var webRequestConfig = new WebRequestConfig ( )
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
DownloadHandler = new DownloadHandlerFile ( path ) ,
EndPoint = network . DownloadUrl
2026-04-20 13:49:36 +08:00
} ;
2026-05-08 11:03:00 +08:00
maxWebRequest = new MaxWebRequest ( webRequestConfig ) ;
yield return maxWebRequest . Send ( webResponse = >
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
if ( webResponse . IsSuccess )
{
importingNetwork = network ;
AssetDatabase . ImportPackage ( path , showImport ) ;
}
else
{
MaxSdkLogger . UserError ( "Failed to download plugin package: " + webResponse . ErrorMessage ) ;
}
} ) ;
2026-04-20 13:49:36 +08:00
}
/// <summary>
/// Cancels the plugin download if one is in progress.
/// </summary>
public void CancelDownload ( )
{
2026-05-08 11:03:00 +08:00
if ( maxWebRequest = = null ) return ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
maxWebRequest . Abort ( ) ;
2026-04-20 13:49:36 +08:00
}
/// <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 ) ;
}
2026-05-08 11:03:00 +08:00
private static void CallImportPackageStartedCallback ( Network network )
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
if ( OnImportPackageStartedCallback = = null ) return ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
OnImportPackageStartedCallback ( network ) ;
2026-04-20 13:49:36 +08:00
}
private static void CallImportPackageCompletedCallback ( Network network )
{
2026-05-08 11:03:00 +08:00
if ( OnImportPackageCompletedCallback = = null ) return ;
2026-04-20 13:49:36 +08:00
2026-05-08 11:03:00 +08:00
OnImportPackageCompletedCallback ( network ) ;
2026-04-20 13:49:36 +08:00
}
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
}
}