using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
#if !UNITY_2020_1_OR_NEWER
using System.Reflection;
#endif
using System.Xml.Linq;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEngine;
namespace AppLovinMax.Scripts.IntegrationManager.Editor
{
[Serializable]
public class PackageInfo
{
// ReSharper disable InconsistentNaming - For JSON Deserialization
public string Name;
public string Version;
}
public interface IPackageManagerClient
{
IEnumerator AddNetwork(Network network, bool showImport);
void RemoveNetwork(Network network);
}
public static class AppLovinPackageManager
{
private const string AppLovinMediationAmazonAdapterDependenciesPath = "Amazon/Scripts/Mediations/AppLovinMediation/Editor/Dependencies.xml";
#if UNITY_2019_2_OR_NEWER
private static readonly IPackageManagerClient _upmPackageManager = new AppLovinUpmPackageManager();
#endif
private static readonly IPackageManagerClient _assetsPackageManager = new AppLovinAssetsPackageManager();
private static IPackageManagerClient PackageManagerClient
{
get
{
#if UNITY_2019_2_OR_NEWER
return AppLovinIntegrationManager.IsPluginInPackageManager ? _upmPackageManager : _assetsPackageManager;
#else
return _assetsPackageManager;
#endif
}
}
internal static PluginData PluginData { get; set; }
///
/// Checks whether or not an adapter with the given version or newer exists.
///
/// The name of the network (the root adapter folder name in "MaxSdk/Mediation/" folder.
/// The min iOS adapter version to check for. Can be null if we want to check for any version.
/// The min android adapter version to check for. Can be null if we want to check for any version.
/// true if an adapter with the min version is installed.
internal static bool IsAdapterInstalled(string adapterName, string iosVersion = null, string androidVersion = null)
{
var dependencyFilePathList = GetAssetPathListForExportPath("MaxSdk/Mediation/" + adapterName + "/Editor/Dependencies.xml");
if (dependencyFilePathList.Count <= 0) return false;
var currentVersion = GetCurrentVersions(dependencyFilePathList);
if (iosVersion != null)
{
var iosVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Ios, iosVersion);
if (iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Lesser)
{
return false;
}
}
if (androidVersion != null)
{
var androidVersionComparison = MaxSdkUtils.CompareVersions(currentVersion.Android, androidVersion);
if (androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Lesser)
{
return false;
}
}
return true;
}
///
/// Checks whether an adapter is installed using the plugin data.
///
/// The plugin data to check for the adapter
/// The name of the network.
/// Whether an adapter is installed in the plugin data
internal static bool IsAdapterInstalled(PluginData pluginData, string adapterName)
{
var network = pluginData.MediatedNetworks.Where(mediatedNetwork => mediatedNetwork.Name.Equals(adapterName)).ToList().FirstOrDefault();
var networkVersion = network != null ? network.CurrentVersions : null;
var currentVersion = networkVersion != null ? networkVersion.Unity : "";
return MaxSdkUtils.IsValidString(currentVersion);
}
///
/// Gets the mediation networks that are currently installed in the project. If using UPM, checks
/// for networks in Packages folder and Mediation folder in case a custom adapter was added to the project.
///
/// A list of the installed mediation network names.
internal static List GetInstalledMediationNetworks()
{
var installedNetworks = new List();
var installedNetworksInAssets = AppLovinAssetsPackageManager.GetInstalledMediationNetworks();
installedNetworks.AddRange(installedNetworksInAssets);
#if UNITY_2019_2_OR_NEWER
var installedNetworksInPackages = AppLovinUpmPackageManager.GetInstalledMediationNetworks();
installedNetworks.AddRange(installedNetworksInPackages);
#endif
if (IsAmazonAppLovinAdapterInstalled())
{
installedNetworks.Add("AmazonAdMarketplace");
}
return installedNetworks;
}
///
/// Adds a network to the project.
///
/// The network to add.
/// Whether to show the import window (only for non UPM)
internal static IEnumerator AddNetwork(Network network, bool showImport)
{
yield return PackageManagerClient.AddNetwork(network, showImport);
AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network));
}
///
/// Removes a network from the project.
///
/// The network to remove.
internal static void RemoveNetwork(Network network)
{
PackageManagerClient.RemoveNetwork(network);
AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network));
}
#region Utility
///
/// Gets the list of all asset paths for a given MAX plugin export path.
///
/// The actual exported path of the asset.
/// The exported path of the MAX plugin asset or an empty list if the asset is not found.
private static List GetAssetPathListForExportPath(string exportPath)
{
var assetLabelToFind = "l:al_max_export_path-" + exportPath.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
var assetGuids = AssetDatabase.FindAssets(assetLabelToFind);
var assetPaths = new List();
foreach (var assetGuid in assetGuids)
{
assetPaths.Add(AssetDatabase.GUIDToAssetPath(assetGuid));
}
return assetPaths.Count <= 0 ? new List() : assetPaths;
}
///
/// Updates the CurrentVersion fields for a given network data object.
///
/// Network for which to update the current versions.
internal static void UpdateCurrentVersions(Network network)
{
var assetPaths = GetAssetPathListForExportPath(network.DependenciesFilePath);
#if UNITY_2019_2_OR_NEWER
if (HasDuplicateAdapters(assetPaths))
{
ShowDeleteDuplicateAdapterPrompt(network);
}
#endif
var currentVersions = GetCurrentVersions(assetPaths);
network.CurrentVersions = currentVersions;
// If AppLovin mediation plugin, get the version from MaxSdk and the latest and current version comparison.
if (network.Name.Equals("APPLOVIN_NETWORK"))
{
network.CurrentVersions.Unity = MaxSdk.Version;
var unityVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Unity, network.LatestVersions.Unity);
var androidVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Android, network.LatestVersions.Android);
var iosVersionComparison = MaxSdkUtils.CompareVersions(network.CurrentVersions.Ios, network.LatestVersions.Ios);
// Overall version is same if all the current and latest (from db) versions are same.
if (unityVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal &&
androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal &&
iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Equal)
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Equal;
}
// One of the installed versions is newer than the latest versions which means that the publisher is on a beta version.
else if (unityVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater ||
androidVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater ||
iosVersionComparison == MaxSdkUtils.VersionComparisonResult.Greater)
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Greater;
}
// We have a new version available if all Android, iOS and Unity has a newer version available in db.
else
{
network.CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Lesser;
}
}
// For all other mediation adapters, get the version comparison using their Unity versions.
else
{
// If adapter is indeed installed, compare the current (installed) and the latest (from db) versions, so that we can determine if the publisher is on an older, current or a newer version of the adapter.
// If the publisher is on a newer version of the adapter than the db version, that means they are on a beta version.
if (MaxSdkUtils.IsValidString(currentVersions.Unity))
{
network.CurrentToLatestVersionComparisonResult = AppLovinIntegrationManagerUtils.CompareUnityMediationVersions(currentVersions.Unity, network.LatestVersions.Unity);
}
if (MaxSdkUtils.IsValidString(network.CurrentVersions.Unity) && AppLovinAutoUpdater.MinAdapterVersions.ContainsKey(network.Name))
{
var comparisonResult = AppLovinIntegrationManagerUtils.CompareUnityMediationVersions(network.CurrentVersions.Unity, AppLovinAutoUpdater.MinAdapterVersions[network.Name]);
// Requires update if current version is lower than the min required version.
network.RequiresUpdate = comparisonResult < 0;
}
else
{
// Reset value so that the Integration manager can hide the alert icon once adapter is updated.
network.RequiresUpdate = false;
}
}
}
#if UNITY_2019_2_OR_NEWER
///
/// Checks whether a network has duplicate adapters installed in both the Assets folder and via UPM.
///
/// The list of paths to the dependencies.xml files
/// True if there are adapters in both the Assets folder and installed via UPM
private static bool HasDuplicateAdapters(List dependencyPaths)
{
var inPackagesFolder = dependencyPaths.Any(path => path.Contains("Packages"));
var inAssetsFolder = dependencyPaths.Any(path => path.Contains("Assets"));
return inPackagesFolder && inAssetsFolder;
}
///
/// Displays a prompt informing the user that duplicate adapters were detected
/// and allows them to choose which version to keep.
///
/// The network that has duplicate adapters installed.
private static void ShowDeleteDuplicateAdapterPrompt(Network network)
{
var keepAssetsAdapter = EditorUtility.DisplayDialog("Duplicate Adapters Detected",
"The " + network.DisplayName + " adapter is installed in both the Assets folder and via UPM. Please choose which version to keep.",
"Keep Assets Folder Version",
"Keep UPM Version");
DeleteDuplicateAdapter(network, keepAssetsAdapter);
}
///
/// Removes a duplicate adapter by either deleting it from the Assets folder
/// or uninstalling it from the Unity Package Manager (UPM).
///
/// The network for which the duplicate adapter is being removed.
/// If true, retains the adapter in the Assets folder and removes the UPM version;
/// otherwise, deletes the adapter from the Assets folder.
internal static void DeleteDuplicateAdapter(Network network, bool keepAssetsAdapter)
{
if (keepAssetsAdapter)
{
var appLovinManifest = AppLovinUpmManifest.Load();
AppLovinUpmPackageManager.RemovePackages(network, appLovinManifest);
appLovinManifest.Save();
}
else
{
foreach (var pluginFilePath in network.PluginFilePaths)
{
var filePath = Path.Combine(AppLovinIntegrationManager.MediationDirectory, pluginFilePath.Replace("MaxSdk/Mediation/", ""));
FileUtil.DeleteFileOrDirectory(filePath);
FileUtil.DeleteFileOrDirectory(filePath + ".meta");
}
}
AppLovinUpmPackageManager.ResolvePackageManager();
}
#endif
///
/// Gets the current versions for a given network's dependency file paths. UPM will have multiple paths
/// for each network - one each for iOS and Android.
///
/// A list of dependency file paths to extract current versions from.
/// Current versions of a given network's dependency files.
private static Versions GetCurrentVersions(List dependencyPaths)
{
var currentVersions = new Versions();
foreach (var dependencyPath in dependencyPaths)
{
GetCurrentVersion(currentVersions, dependencyPath);
}
if (currentVersions.Android != null && currentVersions.Ios != null)
{
currentVersions.Unity = "android_" + currentVersions.Android + "_ios_" + currentVersions.Ios;
}
else if (currentVersions.Android != null)
{
currentVersions.Unity = "android_" + currentVersions.Android;
}
else if (currentVersions.Ios != null)
{
currentVersions.Unity = "ios_" + currentVersions.Ios;
}
return currentVersions;
}
///
/// Extracts the current version of a network from its dependency.xml file.
///
/// The Versions object we are using.
/// The path to the dependency.xml file.
private static void GetCurrentVersion(Versions currentVersions, string dependencyPath)
{
XDocument dependency;
try
{
dependency = XDocument.Load(dependencyPath);
}
#pragma warning disable 0168
catch (IOException exception)
#pragma warning restore 0168
{
// Couldn't find the dependencies file. The plugin is not installed.
return;
}
//
//
//
//
//
//
//
//
string androidVersion = null;
string iosVersion = null;
var dependenciesElement = dependency.Element("dependencies");
if (dependenciesElement != null)
{
var androidPackages = dependenciesElement.Element("androidPackages");
if (androidPackages != null)
{
var adapterPackage = androidPackages.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("androidPackage")
&& element.FirstAttribute.Name.LocalName.Equals("spec")
&& element.FirstAttribute.Value.StartsWith("com.applovin"));
if (adapterPackage != null)
{
androidVersion = adapterPackage.FirstAttribute.Value.Split(':').Last();
// Hack alert: Some Android versions might have square brackets to force a specific version. Remove them if they are detected.
if (androidVersion.StartsWith("["))
{
androidVersion = androidVersion.Trim('[', ']');
}
}
}
var iosPods = dependenciesElement.Element("iosPods");
if (iosPods != null)
{
var adapterPod = iosPods.Descendants().FirstOrDefault(element => element.Name.LocalName.Equals("iosPod")
&& element.FirstAttribute.Name.LocalName.Equals("name")
&& element.FirstAttribute.Value.StartsWith("AppLovin"));
if (adapterPod != null)
{
iosVersion = adapterPod.Attributes().First(attribute => attribute.Name.LocalName.Equals("version")).Value;
}
}
}
if (androidVersion != null)
{
currentVersions.Android = androidVersion;
}
if (iosVersion != null)
{
currentVersions.Ios = iosVersion;
}
}
///
/// Check for the Amazon AppLovin adapter in the project.
///
/// Whether the AppLovin Adapter is installed through the Amazon SDK.
private static bool IsAmazonAppLovinAdapterInstalled()
{
string[] dependenciesFiles = AssetDatabase.FindAssets("t:TextAsset Dependencies", new[] {"Assets"})
.Select(AssetDatabase.GUIDToAssetPath)
.ToArray();
// Use regex to search for Amazon and then AppLovin in the file paths of the dependencies.xml files.
return dependenciesFiles.Any(filePath => filePath.Contains(AppLovinMediationAmazonAdapterDependenciesPath));
}
///
/// Refresh assets and update current versions after a slight delay to allow for Client.Resolve to finish.
///
/// The network that was just installed/removed.
private static IEnumerator RefreshAssetsAtEndOfFrame(Network network)
{
yield return new WaitForEndOfFrame();
UpdateCurrentVersions(network);
AssetDatabase.Refresh();
}
#endregion
}
#if UNITY_2019_2_OR_NEWER
public class AppLovinUpmPackageManager : IPackageManagerClient
{
public const string PackageNamePrefixAppLovin = "com.applovin.mediation.ads";
private const string PackageNamePrefixNetwork = "com.applovin.mediation.adapters";
private const string PackageNamePrefixDsp = "com.applovin.mediation.dsp";
private const float TimeoutFetchPackageCollectionSeconds = 10f;
#if !UNITY_2020_1_OR_NEWER
private static Type packageManagerClientType;
private static MethodInfo packageManagerResolveMethod;
#endif
public static List GetInstalledMediationNetworks()
{
// Return empty list if we failed to get the package list
var packageCollection = GetPackageCollectionSync(TimeoutFetchPackageCollectionSeconds);
if (packageCollection == null)
{
return new List();
}
return packageCollection.Where(package => package.name.StartsWith(PackageNamePrefixNetwork) || package.name.StartsWith(PackageNamePrefixDsp))
.SelectMany(package => package.keywords)
.Where(keyword => keyword.StartsWith("dir:"))
.Select(keyword => keyword.Replace("dir:", ""))
.Distinct()
.ToList();
}
public IEnumerator AddNetwork(Network network, bool showImport)
{
var appLovinManifest = AppLovinUpmManifest.Load();
AddPackages(network, appLovinManifest);
appLovinManifest.Save();
// Remove any versions of the adapter in the Assets folder
AppLovinPackageManager.DeleteDuplicateAdapter(network, false);
ResolvePackageManager();
yield break;
}
public void RemoveNetwork(Network network)
{
var appLovinManifest = AppLovinUpmManifest.Load();
RemovePackages(network, appLovinManifest);
appLovinManifest.Save();
ResolvePackageManager();
}
///
/// Adds a network's packages to the package manager removes any beta version that exists
///
/// The network to add.
/// The AppLovinUpmManifest instance to edit
internal static void AddPackages(Network network, AppLovinUpmManifest appLovinManifest)
{
foreach (var packageInfo in network.Packages)
{
appLovinManifest.AddPackageDependency(packageInfo.Name, packageInfo.Version);
RemoveBetaPackage(packageInfo.Name, appLovinManifest);
}
}
///
/// Removes a network's packages from the package manager
///
/// The network to add.
/// The AppLovinUpmManifest instance to edit
internal static void RemovePackages(Network network, AppLovinUpmManifest appLovinManifest)
{
foreach (var packageInfo in network.Packages)
{
appLovinManifest.RemovePackageDependency(packageInfo.Name);
RemoveBetaPackage(packageInfo.Name, appLovinManifest);
}
}
///
/// Removes the beta version of a package name
///
/// The name of the package to remove a beta for
/// The AppLovinUpmManifest instance to edit
private static void RemoveBetaPackage(string packageName, AppLovinUpmManifest appLovinManifest)
{
var prefix = "";
if (packageName.Contains(PackageNamePrefixNetwork))
{
prefix = PackageNamePrefixNetwork;
}
else if (packageName.Contains(PackageNamePrefixDsp))
{
prefix = PackageNamePrefixDsp;
}
else if (packageName.Contains(PackageNamePrefixAppLovin))
{
prefix = PackageNamePrefixAppLovin;
}
else
{
return;
}
var betaPackageName = packageName.Replace(prefix, prefix + ".beta");
appLovinManifest.RemovePackageDependency(betaPackageName);
}
///
/// Resolves the Unity Package Manager so any changes made to the manifest.json file are reflected in the Unity Editor.
///
internal static void ResolvePackageManager()
{
#if UNITY_2020_1_OR_NEWER
Client.Resolve();
#else
packageManagerClientType = packageManagerClientType ?? typeof(Client);
if (packageManagerClientType != null)
{
packageManagerResolveMethod = packageManagerResolveMethod ?? packageManagerClientType.GetMethod("Resolve", BindingFlags.NonPublic | BindingFlags.Static);
}
if (packageManagerResolveMethod != null)
{
packageManagerResolveMethod.Invoke(null, null);
}
#endif
}
///
/// Gets the PackageCollection from the Unity Package Manager synchronously.
///
/// How long to wait before exiting with a timeout error
///
private static PackageCollection GetPackageCollectionSync(float timeoutSeconds = -1)
{
var request = Client.List();
// Just wait till the request is complete
var now = DateTime.Now;
while (!request.IsCompleted)
{
// Wait indefinitely if there is no timeout set.
if (timeoutSeconds < 0) continue;
var delta = DateTime.Now - now;
if (delta.TotalSeconds > timeoutSeconds)
{
MaxSdkLogger.UserError("Failed to list UPM packages: Timeout");
break;
}
}
if (!request.IsCompleted)
{
return null;
}
if (request.Status >= StatusCode.Failure)
{
MaxSdkLogger.UserError("Failed to list packages: " + request.Error.message);
return null;
}
return (request.Status == StatusCode.Success) ? request.Result : null;
}
}
#endif
public class AppLovinAssetsPackageManager : IPackageManagerClient
{
public static List GetInstalledMediationNetworks()
{
var maxMediationDirectory = AppLovinIntegrationManager.MediationDirectory;
if (!Directory.Exists(maxMediationDirectory)) return new List();
var mediationNetworkDirectories = Directory.GetDirectories(maxMediationDirectory);
return mediationNetworkDirectories.Select(Path.GetFileName).ToList();
}
public IEnumerator AddNetwork(Network network, bool showImport)
{
yield return AppLovinIntegrationManager.Instance.DownloadPlugin(network, showImport);
}
public void RemoveNetwork(Network network)
{
foreach (var pluginFilePath in network.PluginFilePaths)
{
var filePath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, pluginFilePath);
FileUtil.DeleteFileOrDirectory(filePath);
FileUtil.DeleteFileOrDirectory(filePath + ".meta");
}
}
}
}