feat:1、创建项目
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e01816bc45c944d03afb95d035caf0e1
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0da229e279400497786c39933fe02e52
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// AppLovinAutoUpdater.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 1/27/20.
|
||||
// Copyright © 2020 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles auto updates for AppLovin MAX plugin.
|
||||
/// </summary>
|
||||
public static class AppLovinAutoUpdater
|
||||
{
|
||||
public const string KeyAutoUpdateEnabled = "com.applovin.auto_update_enabled";
|
||||
private const string KeyLastUpdateCheckTime = "com.applovin.last_update_check_time_v2"; // Updated to v2 to force adapter version checks in plugin version 3.1.10.
|
||||
private static readonly DateTime EpochTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly int SecondsInADay = (int) TimeSpan.FromDays(1).TotalSeconds;
|
||||
|
||||
// TODO: Make this list dynamic.
|
||||
public static readonly Dictionary<string, string> MinAdapterVersions = new Dictionary<string, string>()
|
||||
{
|
||||
{"ADMOB_NETWORK", "android_23.3.0.1_ios_11.9.0.1"},
|
||||
{"BIDMACHINE_NETWORK", "android_3.0.1.1_ios_3.0.0.0.1"},
|
||||
{"CHARTBOOST_NETWORK", "android_9.7.0.3_ios_9.7.0.2"},
|
||||
{"FACEBOOK_MEDIATE", "android_6.17.0.1_ios_6.15.2.1"},
|
||||
{"FYBER_NETWORK", "android_8.3.1.1_ios_8.3.2.1"},
|
||||
{"GOOGLE_AD_MANAGER_NETWORK", "android_23.3.0.1_ios_11.9.0.1"},
|
||||
{"HYPRMX_NETWORK", "android_6.4.2.1_ios_6.4.1.0.1"},
|
||||
{"INMOBI_NETWORK", "android_10.7.7.1_ios_10.7.5.1"},
|
||||
{"IRONSOURCE_NETWORK", "android_8.3.0.0.2_ios_8.3.0.0.1"},
|
||||
{"LINE_NETWORK", "android_2024.8.27.1_ios_2.8.20240827.1"},
|
||||
{"MINTEGRAL_NETWORK", "android_16.8.51.1_ios_7.7.2.0.1"},
|
||||
{"MOBILEFUSE_NETWORK", "android_1.7.6.1_ios_1.7.6.1"},
|
||||
{"MOLOCO_NETWORK", "android_3.1.0.1_ios_3.1.3.1"},
|
||||
{"MYTARGET_NETWORK", "android_5.22.1.1_ios_5.21.7.1"},
|
||||
{"PUBMATIC_NETWORK", "android_3.9.0.2_ios_3.9.0.2"},
|
||||
{"SMAATO_NETWORK", "android_22.7.0.1_ios_22.8.4.1"},
|
||||
{"TIKTOK_NETWORK", "android_6.2.0.5.2_ios_6.2.0.7.2"},
|
||||
{"UNITY_NETWORK", "android_4.12.2.1_ios_4.12.2.1"},
|
||||
{"VERVE_NETWORK", "android_3.0.4.1_ios_3.0.4.1"},
|
||||
{"VUNGLE_NETWORK", "android_7.4.1.1_ios_7.4.1.1"},
|
||||
{"YANDEX_NETWORK", "android_7.4.0.1_ios_2.18.0.1"},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a new version of the plugin is available and prompts the user to update if one is available.
|
||||
/// </summary>
|
||||
public static void Update()
|
||||
{
|
||||
var now = (int) (DateTime.UtcNow - EpochTime).TotalSeconds;
|
||||
if (EditorPrefs.HasKey(KeyLastUpdateCheckTime))
|
||||
{
|
||||
var elapsedTime = now - EditorPrefs.GetInt(KeyLastUpdateCheckTime);
|
||||
|
||||
// Check if we have checked for a new version in the last 24 hrs and skip update if we have.
|
||||
if (elapsedTime < SecondsInADay) return;
|
||||
}
|
||||
|
||||
// Update last checked time.
|
||||
EditorPrefs.SetInt(KeyLastUpdateCheckTime, now);
|
||||
|
||||
// Load the plugin data
|
||||
AppLovinEditorCoroutine.StartCoroutine(AppLovinIntegrationManager.Instance.LoadPluginData(data =>
|
||||
{
|
||||
if (data == null) return;
|
||||
|
||||
ShowPluginUpdateDialogIfNeeded(data);
|
||||
ShowNetworkAdaptersUpdateDialogIfNeeded(data.MediatedNetworks);
|
||||
ShowGoogleNetworkAdaptersUpdateDialogIfNeeded(data.MediatedNetworks);
|
||||
}));
|
||||
}
|
||||
|
||||
private static void ShowPluginUpdateDialogIfNeeded(PluginData data)
|
||||
{
|
||||
// Check if publisher has disabled auto update.
|
||||
if (!EditorPrefs.GetBool(KeyAutoUpdateEnabled, true)) return;
|
||||
|
||||
// Check if the current and latest version are the same or if the publisher is on a newer version (on beta). If so, skip update.
|
||||
var comparison = data.AppLovinMax.CurrentToLatestVersionComparisonResult;
|
||||
if (comparison == MaxSdkUtils.VersionComparisonResult.Equal || comparison == MaxSdkUtils.VersionComparisonResult.Greater) return;
|
||||
|
||||
// A new version of the plugin is available. Show a dialog to the publisher.
|
||||
var option = EditorUtility.DisplayDialogComplex(
|
||||
"AppLovin MAX Plugin Update",
|
||||
"A new version of AppLovin MAX plugin is available for download. Update now?",
|
||||
"Download",
|
||||
"Not Now",
|
||||
"Don't Ask Again");
|
||||
|
||||
if (option == 0) // Download
|
||||
{
|
||||
MaxSdkLogger.UserDebug("Downloading plugin...");
|
||||
AppLovinEditorCoroutine.StartCoroutine(AppLovinIntegrationManager.Instance.DownloadPlugin(data.AppLovinMax));
|
||||
}
|
||||
else if (option == 1) // Not Now
|
||||
{
|
||||
// Do nothing
|
||||
MaxSdkLogger.UserDebug("Update postponed.");
|
||||
}
|
||||
else if (option == 2) // Don't Ask Again
|
||||
{
|
||||
MaxSdkLogger.UserDebug("Auto Update disabled. You can enable it again from the AppLovin Integration Manager");
|
||||
EditorPrefs.SetBool(KeyAutoUpdateEnabled, false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ShowNetworkAdaptersUpdateDialogIfNeeded(Network[] networks)
|
||||
{
|
||||
var networksToUpdate = networks.Where(network => network.RequiresUpdate).ToList();
|
||||
|
||||
// If all networks are above the required version, do nothing.
|
||||
if (networksToUpdate.Count <= 0) return;
|
||||
|
||||
// We found a few adapters that are not compatible with the current SDK, show alert.
|
||||
var message = "The following network adapters are not compatible with the current version of AppLovin MAX Plugin:\n";
|
||||
foreach (var networkName in networksToUpdate)
|
||||
{
|
||||
message += "\n- ";
|
||||
message += networkName.DisplayName + " (Requires " + MinAdapterVersions[networkName.Name] + " or newer)";
|
||||
}
|
||||
|
||||
message += "\n\nPlease update them to the latest versions to avoid any issues.";
|
||||
|
||||
AppLovinIntegrationManager.ShowBuildFailureDialog(message);
|
||||
}
|
||||
|
||||
private static void ShowGoogleNetworkAdaptersUpdateDialogIfNeeded(Network[] networks)
|
||||
{
|
||||
// AdMob and GAM use the same SDKs so their adapters should use the same underlying SDK version.
|
||||
var googleNetwork = networks.FirstOrDefault(network => network.Name.Equals("ADMOB_NETWORK"));
|
||||
var googleAdManagerNetwork = networks.FirstOrDefault(network => network.Name.Equals("GOOGLE_AD_MANAGER_NETWORK"));
|
||||
|
||||
// If both AdMob and GAM are not integrated, do nothing.
|
||||
if (googleNetwork == null || string.IsNullOrEmpty(googleNetwork.CurrentVersions.Unity) ||
|
||||
googleAdManagerNetwork == null || string.IsNullOrEmpty(googleAdManagerNetwork.CurrentVersions.Unity)) return;
|
||||
|
||||
var isAndroidVersionCompatible = GoogleNetworkAdaptersCompatible(googleNetwork.CurrentVersions.Android, googleAdManagerNetwork.CurrentVersions.Android, "19.8.0.0");
|
||||
var isIosVersionCompatible = GoogleNetworkAdaptersCompatible(googleNetwork.CurrentVersions.Ios, googleAdManagerNetwork.CurrentVersions.Ios, "8.0.0.0");
|
||||
|
||||
if (isAndroidVersionCompatible && isIosVersionCompatible) return;
|
||||
|
||||
var message = "You may see unexpected errors if you use different versions of the AdMob and Google Ad Manager adapter SDKs. " +
|
||||
"AdMob and Google Ad Manager share the same SDKs.\n\n" +
|
||||
"You can be sure that you are using the same SDK for both if the first three numbers in each adapter version match.";
|
||||
|
||||
AppLovinIntegrationManager.ShowBuildFailureDialog(message);
|
||||
}
|
||||
|
||||
private static bool GoogleNetworkAdaptersCompatible(string googleVersion, string googleAdManagerVersion, string breakingVersion)
|
||||
{
|
||||
var googleResult = MaxSdkUtils.CompareVersions(googleVersion, breakingVersion);
|
||||
var googleAdManagerResult = MaxSdkUtils.CompareVersions(googleAdManagerVersion, breakingVersion);
|
||||
|
||||
// If one is less than the breaking version and the other is not, they are not compatible.
|
||||
if (googleResult == MaxSdkUtils.VersionComparisonResult.Lesser &&
|
||||
googleAdManagerResult != MaxSdkUtils.VersionComparisonResult.Lesser) return false;
|
||||
|
||||
if (googleAdManagerResult == MaxSdkUtils.VersionComparisonResult.Lesser &&
|
||||
googleResult != MaxSdkUtils.VersionComparisonResult.Lesser) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bcdd5226273242c5bd9ec79568202e6
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinAutoUpdater.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// AppLovinBuildPostProcessor.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 10/30/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_IOS || UNITY_IPHONE
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper class to run command line tools.
|
||||
///
|
||||
/// TODO: Currently only supports shell (Linux). Add support for Windows machines.
|
||||
/// </summary>
|
||||
public static class AppLovinCommandLine
|
||||
{
|
||||
/// <summary>
|
||||
/// Result obtained by running a command line command.
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard output stream from command line.
|
||||
/// </summary>
|
||||
public string StandardOutput;
|
||||
|
||||
/// <summary>
|
||||
/// Standard error stream from command line.
|
||||
/// </summary>
|
||||
public string StandardError;
|
||||
|
||||
/// <summary>
|
||||
/// Exit code returned from command line.
|
||||
/// </summary>
|
||||
public int ExitCode;
|
||||
|
||||
/// <summary>
|
||||
/// The description of the result that can be used for error logging.
|
||||
/// </summary>
|
||||
public string Message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a command line tool using the provided <see cref="toolPath"/> and <see cref="arguments"/>.
|
||||
/// </summary>
|
||||
/// <param name="toolPath">The tool path to run</param>
|
||||
/// <param name="arguments">The arguments to be passed to the command line tool</param>
|
||||
/// <param name="workingDirectory">The directory from which to run this command.</param>
|
||||
/// <returns></returns>
|
||||
public static Result Run(string toolPath, string arguments, string workingDirectory)
|
||||
{
|
||||
var stdoutFileName = Path.GetTempFileName();
|
||||
var stderrFileName = Path.GetTempFileName();
|
||||
|
||||
var process = new Process();
|
||||
process.StartInfo.UseShellExecute = true;
|
||||
process.StartInfo.CreateNoWindow = false;
|
||||
process.StartInfo.RedirectStandardInput = false;
|
||||
process.StartInfo.RedirectStandardOutput = false;
|
||||
process.StartInfo.RedirectStandardError = false;
|
||||
|
||||
process.StartInfo.WorkingDirectory = workingDirectory;
|
||||
process.StartInfo.FileName = "bash";
|
||||
process.StartInfo.Arguments = string.Format("-l -c '\"{0}\" {1} 1> {2} 2> {3}'", toolPath, arguments, stdoutFileName, stderrFileName);
|
||||
process.Start();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
var stdout = File.ReadAllText(stdoutFileName);
|
||||
var stderr = File.ReadAllText(stderrFileName);
|
||||
|
||||
File.Delete(stdoutFileName);
|
||||
File.Delete(stderrFileName);
|
||||
|
||||
var result = new Result();
|
||||
result.StandardOutput = stdout;
|
||||
result.StandardError = stderr;
|
||||
result.ExitCode = process.ExitCode;
|
||||
|
||||
var messagePrefix = result.ExitCode == 0 ? "Command executed successfully" : "Failed to run command";
|
||||
result.Message = string.Format("{0}: '{1} {2}'\nstdout: {3}\nstderr: {4}\nExit code: {5}", messagePrefix, toolPath, arguments, stdout, stderr, process.ExitCode);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d0aa55f9a7d2440f871dfb256372a33
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinCommandLine.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// AppLovinEditorCoroutine.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 7/25/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A coroutine that can update based on editor application update.
|
||||
/// </summary>
|
||||
public class AppLovinEditorCoroutine
|
||||
{
|
||||
/// <summary>
|
||||
/// Keeps track of the coroutine currently running.
|
||||
/// </summary>
|
||||
private IEnumerator enumerator;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps track of coroutines that have yielded to the current enumerator.
|
||||
/// </summary>
|
||||
private readonly List<IEnumerator> history = new List<IEnumerator>();
|
||||
|
||||
private AppLovinEditorCoroutine(IEnumerator enumerator)
|
||||
{
|
||||
this.enumerator = enumerator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and starts a coroutine.
|
||||
/// </summary>
|
||||
/// <param name="enumerator">The coroutine to be started</param>
|
||||
/// <returns>The coroutine that has been started.</returns>
|
||||
public static AppLovinEditorCoroutine StartCoroutine(IEnumerator enumerator)
|
||||
{
|
||||
var coroutine = new AppLovinEditorCoroutine(enumerator);
|
||||
coroutine.Start();
|
||||
return coroutine;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
EditorApplication.update += OnEditorUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the coroutine.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (EditorApplication.update == null) return;
|
||||
|
||||
EditorApplication.update -= OnEditorUpdate;
|
||||
}
|
||||
|
||||
private void OnEditorUpdate()
|
||||
{
|
||||
if (enumerator.MoveNext())
|
||||
{
|
||||
// If there is a coroutine to yield for inside the coroutine, add the initial one to history and continue the second one
|
||||
if (enumerator.Current is IEnumerator)
|
||||
{
|
||||
history.Add(enumerator);
|
||||
enumerator = (IEnumerator) enumerator.Current;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Current coroutine has ended, check if we have more coroutines in history to be run.
|
||||
if (history.Count == 0)
|
||||
{
|
||||
// No more coroutines to run, stop updating.
|
||||
Stop();
|
||||
}
|
||||
// Step out and finish the code in the coroutine that yielded to it
|
||||
else
|
||||
{
|
||||
var index = history.Count - 1;
|
||||
enumerator = history[index];
|
||||
history.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95747c688378548eeb92aeb528ac96ff
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinEditorCoroutine.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,146 @@
|
||||
//
|
||||
// MaxInitialization.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Thomas So on 5/24/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class AppLovinInitialize
|
||||
{
|
||||
private static readonly List<string> ObsoleteNetworks = new List<string>
|
||||
{
|
||||
"AdColony",
|
||||
"Criteo",
|
||||
"Nend",
|
||||
"Snap",
|
||||
"Tapjoy",
|
||||
"VerizonAds",
|
||||
"VoodooAds"
|
||||
};
|
||||
|
||||
private static readonly List<string> ObsoleteFileExportPathsToDelete = new List<string>
|
||||
{
|
||||
// The `MaxSdk/Scripts/Editor` folder contents have been moved into `MaxSdk/Scripts/IntegrationManager/Editor`.
|
||||
"MaxSdk/Scripts/Editor",
|
||||
"MaxSdk/Scripts/Editor.meta",
|
||||
|
||||
// The `EventSystemChecker` has been renamed to `MaxEventSystemChecker`.
|
||||
"MaxSdk/Scripts/EventSystemChecker.cs",
|
||||
"MaxSdk/Scripts/EventSystemChecker.cs.meta",
|
||||
|
||||
// Google AdMob adapter pre/post process scripts. The logic has been migrated to the main plugin.
|
||||
"MaxSdk/Mediation/Google/Editor/MaxGoogleInitialize.cs",
|
||||
"MaxSdk/Mediation/Google/Editor/MaxGoogleInitialize.cs.meta",
|
||||
"MaxSdk/Mediation/Google/Editor/MaxMediationGoogleUtils.cs",
|
||||
"MaxSdk/Mediation/Google/Editor/MaxMediationGoogleUtils.cs.meta",
|
||||
"MaxSdk/Mediation/Google/Editor/PostProcessor.cs",
|
||||
"MaxSdk/Mediation/Google/Editor/PostProcessor.cs.meta",
|
||||
"MaxSdk/Mediation/Google/Editor/PreProcessor.cs",
|
||||
"MaxSdk/Mediation/Google/Editor/PreProcessor.cs.meta",
|
||||
"MaxSdk/Mediation/Google/Editor/MaxSdk.Mediation.Google.Editor.asmdef",
|
||||
"MaxSdk/Mediation/Google/MaxSdk.Mediation.Google.Editor.asmdef.meta",
|
||||
"Plugins/Android/MaxMediationGoogle.androidlib",
|
||||
"Plugins/Android/MaxMediationGoogle.androidlib.meta",
|
||||
|
||||
// Google Ad Manager adapter pre/post process scripts. The logic has been migrated to the main plugin.
|
||||
"MaxSdk/Mediation/GoogleAdManager/Editor/MaxGoogleAdManagerInitialize.cs",
|
||||
"MaxSdk/Mediation/GoogleAdManager/Editor/MaxGoogleAdManagerInitialize.cs.meta",
|
||||
"MaxSdk/Mediation/GoogleAdManager/Editor/PostProcessor.cs",
|
||||
"MaxSdk/Mediation/GoogleAdManager/Editor/PostProcessor.cs.meta",
|
||||
"MaxSdk/Mediation/GoogleAdManager/Editor/MaxSdk.Mediation.GoogleAdManager.Editor.asmdef",
|
||||
"MaxSdk/Mediation/GoogleAdManager/Editor/MaxSdk.Mediation.GoogleAdManager.Editor.asmdef.meta",
|
||||
"Plugins/Android/MaxMediationGoogleAdManager.androidlib",
|
||||
"Plugins/Android/MaxMediationGoogleAdManager.androidlib.meta",
|
||||
|
||||
// The `VariableService` has been removed.
|
||||
"MaxSdk/Scripts/MaxVariableServiceAndroid.cs",
|
||||
"MaxSdk/Scripts/MaxVariableServiceAndroid.cs.meta",
|
||||
"MaxSdk/Scripts/MaxVariableServiceiOS.cs",
|
||||
"MaxSdk/Scripts/MaxVariableServiceiOS.cs.meta",
|
||||
"MaxSdk/Scripts/MaxVariableServiceUnityEditor.cs",
|
||||
"MaxSdk/Scripts/MaxVariableServiceUnityEditor.cs.meta",
|
||||
|
||||
// The `MaxSdk/Scripts/Editor` folder contents have been moved into `MaxSdk/Scripts/IntegrationManager/Editor`.
|
||||
"MaxSdk/Version.md",
|
||||
"MaxSdk/Version.md.meta",
|
||||
|
||||
// The alert_icon.png has been renamed to error_icon.png.
|
||||
"MaxSdk/Resources/Images/alert_icon.png",
|
||||
"MaxSdk/Resources/Images/alert_icon.png.meta",
|
||||
|
||||
// `TargetingData` has been removed and we no longer set `UserSegment` through the Unity Plugin.
|
||||
"MaxSdk/Scripts/MaxUserSegment.cs",
|
||||
"MaxSdk/Scripts/MaxUserSegment.cs.meta",
|
||||
"MaxSdk/Scripts/MaxTargetingData.cs",
|
||||
"MaxSdk/Scripts/MaxTargetingData.cs.meta"
|
||||
};
|
||||
|
||||
static AppLovinInitialize()
|
||||
{
|
||||
// Don't run obsolete file cleanup logic when entering play mode.
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode) return;
|
||||
|
||||
#if UNITY_IOS
|
||||
// Check that the publisher is targeting iOS 9.0+
|
||||
if (!PlayerSettings.iOS.targetOSVersionString.StartsWith("9.") && !PlayerSettings.iOS.targetOSVersionString.StartsWith("1"))
|
||||
{
|
||||
MaxSdkLogger.UserError("Detected iOS project version less than iOS 9 - The AppLovin MAX SDK WILL NOT WORK ON < iOS9!!!");
|
||||
}
|
||||
#endif
|
||||
|
||||
var isPluginInPackageManager = AppLovinIntegrationManager.IsPluginInPackageManager;
|
||||
if (!isPluginInPackageManager)
|
||||
{
|
||||
var changesMade = false;
|
||||
foreach (var obsoleteFileExportPathToDelete in ObsoleteFileExportPathsToDelete)
|
||||
{
|
||||
var pathToDelete = MaxSdkUtils.GetAssetPathForExportPath(obsoleteFileExportPathToDelete);
|
||||
if (CheckExistence(pathToDelete))
|
||||
{
|
||||
MaxSdkLogger.UserDebug("Deleting obsolete file '" + pathToDelete + "' that is no longer needed.");
|
||||
FileUtil.DeleteFileOrDirectory(pathToDelete);
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
|
||||
var pluginParentDir = AppLovinIntegrationManager.PluginParentDirectory;
|
||||
// Check if any obsolete networks are installed
|
||||
foreach (var obsoleteNetwork in ObsoleteNetworks)
|
||||
{
|
||||
var networkDir = Path.Combine(pluginParentDir, "MaxSdk/Mediation/" + obsoleteNetwork);
|
||||
if (CheckExistence(networkDir))
|
||||
{
|
||||
MaxSdkLogger.UserDebug("Deleting obsolete network " + obsoleteNetwork + " from path " + networkDir + "...");
|
||||
FileUtil.DeleteFileOrDirectory(networkDir);
|
||||
FileUtil.DeleteFileOrDirectory(networkDir + ".meta");
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh UI
|
||||
if (changesMade)
|
||||
{
|
||||
AssetDatabase.Refresh();
|
||||
MaxSdkLogger.UserDebug("Obsolete networks and files removed.");
|
||||
}
|
||||
}
|
||||
|
||||
AppLovinAutoUpdater.Update();
|
||||
}
|
||||
|
||||
private static bool CheckExistence(string location)
|
||||
{
|
||||
return File.Exists(location) ||
|
||||
Directory.Exists(location) ||
|
||||
(location.EndsWith("/*") && Directory.Exists(Path.GetDirectoryName(location)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6b7dce7fe193a4058bc51d9f4d3a2aed
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinInitialize.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,525 @@
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 a plugin package's import is started.
|
||||
/// </summary>
|
||||
internal delegate void ImportPackageStartedCallback(Network network);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to be called when a plugin package is finished importing.
|
||||
/// </summary>
|
||||
/// <param name="network">The network data for which the package is imported.</param>
|
||||
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;
|
||||
|
||||
/// <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 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
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<PluginData>(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;
|
||||
}
|
||||
|
||||
/// <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 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the plugin download if one is in progress.
|
||||
/// </summary>
|
||||
public void CancelDownload()
|
||||
{
|
||||
if (maxWebRequest == null) return;
|
||||
|
||||
maxWebRequest.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 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5b874c1e65274159bcc0dc15d9458cc
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinIntegrationManager.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
public static class AppLovinIntegrationManagerUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Compares AppLovin MAX Unity mediation adapter plugin versions. Returns <see cref="MaxSdkUtils.VersionComparisonResult.Lesser"/>, <see cref="MaxSdkUtils.VersionComparisonResult.Equal"/>,
|
||||
/// or <see cref="MaxSdkUtils.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="MaxSdkUtils.VersionComparisonResult.Lesser"/> if versionA is less than versionB.
|
||||
/// <see cref="MaxSdkUtils.VersionComparisonResult.Equal"/> if versionA and versionB are equal.
|
||||
/// <see cref="MaxSdkUtils.VersionComparisonResult.Greater"/> if versionA is greater than versionB.
|
||||
/// </returns>
|
||||
internal static MaxSdkUtils.VersionComparisonResult CompareUnityMediationVersions(string versionA, string versionB)
|
||||
{
|
||||
if (versionA.Equals(versionB)) return MaxSdkUtils.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 = MaxSdkUtils.VersionComparisonResult.Equal;
|
||||
if (versionA.Contains("android") && versionB.Contains("android"))
|
||||
{
|
||||
var androidVersionA = versionAComponents[1];
|
||||
var androidVersionB = versionBComponents[1];
|
||||
androidComparison = MaxSdkUtils.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 = MaxSdkUtils.VersionComparisonResult.Greater;
|
||||
|
||||
// Remove the Android version component so that iOS versions can be processed.
|
||||
versionAComponents.RemoveRange(0, 2);
|
||||
}
|
||||
else if (versionB.Contains("android"))
|
||||
{
|
||||
androidComparison = MaxSdkUtils.VersionComparisonResult.Lesser;
|
||||
|
||||
// Remove the Android version component so that iOS version can be processed.
|
||||
versionBComponents.RemoveRange(0, 2);
|
||||
}
|
||||
|
||||
var iosComparison = MaxSdkUtils.VersionComparisonResult.Equal;
|
||||
if (versionA.Contains("ios") && versionB.Contains("ios"))
|
||||
{
|
||||
var iosVersionA = versionAComponents[1];
|
||||
var iosVersionB = versionBComponents[1];
|
||||
iosComparison = MaxSdkUtils.CompareVersions(iosVersionA, iosVersionB);
|
||||
}
|
||||
else if (versionA.Contains("ios"))
|
||||
{
|
||||
iosComparison = MaxSdkUtils.VersionComparisonResult.Greater;
|
||||
}
|
||||
else if (versionB.Contains("ios"))
|
||||
{
|
||||
iosComparison = MaxSdkUtils.VersionComparisonResult.Lesser;
|
||||
}
|
||||
|
||||
// If either one of the Android or iOS version is greater, the entire version should be greater.
|
||||
return (androidComparison == MaxSdkUtils.VersionComparisonResult.Greater || iosComparison == MaxSdkUtils.VersionComparisonResult.Greater) ? MaxSdkUtils.VersionComparisonResult.Greater : MaxSdkUtils.VersionComparisonResult.Lesser;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96efb4dba39eb48d2a60afab69786e6a
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinIntegrationManagerUtils.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
+14
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84c61b4c05e114cec91df447d1b585f8
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinIntegrationManagerWindow.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,307 @@
|
||||
//
|
||||
// AppLovinInternalSettings.cs
|
||||
// AppLovin User Engagement Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 9/15/22.
|
||||
// Copyright © 2022 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="ScriptableObject"/> representing the AppLovin internal settings that can be set in the Integration Manager Window.
|
||||
///
|
||||
/// The scriptable object asset is saved under ProjectSettings as <c>AppLovinInternalSettings.json</c>.
|
||||
/// </summary>
|
||||
public class AppLovinInternalSettings : ScriptableObject
|
||||
{
|
||||
private static AppLovinInternalSettings instance;
|
||||
|
||||
private const string DefaultUserTrackingDescriptionEn = "This uses device info for more personalized ads and content";
|
||||
private const string DefaultUserTrackingDescriptionDe = "Dies benutzt Gerätinformationen für relevantere Werbeinhalte";
|
||||
private const string DefaultUserTrackingDescriptionEs = "Esto utiliza la información del dispositivo para anuncios y contenido más personalizados";
|
||||
private const string DefaultUserTrackingDescriptionFr = "Cela permet d'utiliser les informations du téléphone pour afficher des contenus publicitaires plus pertinents.";
|
||||
private const string DefaultUserTrackingDescriptionJa = "これはユーザーデータをもとに、より関連性の高い広告コンテンツをお客様に提供します";
|
||||
private const string DefaultUserTrackingDescriptionKo = "보다 개인화된 광고 및 콘텐츠를 위해 기기 정보를 사용합니다.";
|
||||
private const string DefaultUserTrackingDescriptionZhHans = "我们使用设备信息来提供个性化的广告和内容。";
|
||||
private const string DefaultUserTrackingDescriptionZhHant = "我們使用設備信息來提供個性化的廣告和內容。";
|
||||
|
||||
[SerializeField] private bool consentFlowEnabled;
|
||||
[SerializeField] private string consentFlowPrivacyPolicyUrl = string.Empty;
|
||||
[SerializeField] private string consentFlowTermsOfServiceUrl = string.Empty;
|
||||
[SerializeField] private bool shouldShowTermsAndPrivacyPolicyAlertInGDPR;
|
||||
[SerializeField] private bool overrideDefaultUserTrackingUsageDescriptions;
|
||||
[SerializeField] private MaxSdkBase.ConsentFlowUserGeography debugUserGeography;
|
||||
[SerializeField] private string userTrackingUsageDescriptionEn = string.Empty;
|
||||
[SerializeField] private bool userTrackingUsageLocalizationEnabled;
|
||||
[SerializeField] private string userTrackingUsageDescriptionDe = string.Empty;
|
||||
[SerializeField] private string userTrackingUsageDescriptionEs = string.Empty;
|
||||
[SerializeField] private string userTrackingUsageDescriptionFr = string.Empty;
|
||||
[SerializeField] private string userTrackingUsageDescriptionJa = string.Empty;
|
||||
[SerializeField] private string userTrackingUsageDescriptionKo = string.Empty;
|
||||
[SerializeField] private string userTrackingUsageDescriptionZhHans = string.Empty;
|
||||
[SerializeField] private string userTrackingUsageDescriptionZhHant = string.Empty;
|
||||
|
||||
private const string SettingsFilePath = "ProjectSettings/AppLovinInternalSettings.json";
|
||||
|
||||
public static AppLovinInternalSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance != null) return instance;
|
||||
|
||||
instance = CreateInstance<AppLovinInternalSettings>();
|
||||
|
||||
var projectRootPath = Path.GetDirectoryName(Application.dataPath);
|
||||
var settingsFilePath = Path.Combine(projectRootPath, SettingsFilePath);
|
||||
if (!File.Exists(settingsFilePath))
|
||||
{
|
||||
instance.Save();
|
||||
return instance;
|
||||
}
|
||||
|
||||
var settingsJson = File.ReadAllText(settingsFilePath);
|
||||
if (string.IsNullOrEmpty(settingsJson))
|
||||
{
|
||||
instance.Save();
|
||||
return instance;
|
||||
}
|
||||
|
||||
JsonUtility.FromJsonOverwrite(settingsJson, instance);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var settingsJson = JsonUtility.ToJson(instance);
|
||||
try
|
||||
{
|
||||
var projectRootPath = Path.GetDirectoryName(Application.dataPath);
|
||||
var settingsFilePath = Path.Combine(projectRootPath, SettingsFilePath);
|
||||
File.WriteAllText(settingsFilePath, settingsJson);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to save internal settings.");
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not AppLovin Consent Flow is enabled.
|
||||
/// </summary>
|
||||
public bool ConsentFlowEnabled
|
||||
{
|
||||
get { return consentFlowEnabled; }
|
||||
set
|
||||
{
|
||||
var previousValue = consentFlowEnabled;
|
||||
consentFlowEnabled = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
// If the value didn't change, we don't need to update anything.
|
||||
if (previousValue) return;
|
||||
|
||||
UserTrackingUsageDescriptionEn = DefaultUserTrackingDescriptionEn;
|
||||
UserTrackingUsageLocalizationEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ConsentFlowPrivacyPolicyUrl = string.Empty;
|
||||
ConsentFlowTermsOfServiceUrl = string.Empty;
|
||||
UserTrackingUsageDescriptionEn = string.Empty;
|
||||
UserTrackingUsageLocalizationEnabled = false;
|
||||
OverrideDefaultUserTrackingUsageDescriptions = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A URL pointing to the Privacy Policy for the app to be shown when prompting the user for consent.
|
||||
/// </summary>
|
||||
public string ConsentFlowPrivacyPolicyUrl
|
||||
{
|
||||
get { return consentFlowPrivacyPolicyUrl; }
|
||||
set { consentFlowPrivacyPolicyUrl = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional URL pointing to the Terms of Service for the app to be shown when prompting the user for consent.
|
||||
/// </summary>
|
||||
public string ConsentFlowTermsOfServiceUrl
|
||||
{
|
||||
get { return consentFlowTermsOfServiceUrl; }
|
||||
set { consentFlowTermsOfServiceUrl = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to show the Terms and Privacy Policy alert in GDPR regions prior to presenting the CMP prompt.
|
||||
/// </summary>
|
||||
public bool ShouldShowTermsAndPrivacyPolicyAlertInGDPR
|
||||
{
|
||||
get { return shouldShowTermsAndPrivacyPolicyAlertInGDPR; }
|
||||
set { shouldShowTermsAndPrivacyPolicyAlertInGDPR = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in English to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see <see href="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Apple's documentation</see>.
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionEn
|
||||
{
|
||||
get { return userTrackingUsageDescriptionEn; }
|
||||
set { userTrackingUsageDescriptionEn = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional string to set debug user geography
|
||||
/// </summary>
|
||||
public MaxSdkBase.ConsentFlowUserGeography DebugUserGeography
|
||||
{
|
||||
get { return debugUserGeography; }
|
||||
set { debugUserGeography = value; }
|
||||
}
|
||||
|
||||
public bool OverrideDefaultUserTrackingUsageDescriptions
|
||||
{
|
||||
get { return overrideDefaultUserTrackingUsageDescriptions; }
|
||||
set
|
||||
{
|
||||
var previousValue = overrideDefaultUserTrackingUsageDescriptions;
|
||||
overrideDefaultUserTrackingUsageDescriptions = value;
|
||||
|
||||
if (!value)
|
||||
{
|
||||
if (!previousValue) return;
|
||||
|
||||
UserTrackingUsageDescriptionEn = DefaultUserTrackingDescriptionEn;
|
||||
UserTrackingUsageDescriptionDe = DefaultUserTrackingDescriptionDe;
|
||||
UserTrackingUsageDescriptionEs = DefaultUserTrackingDescriptionEs;
|
||||
UserTrackingUsageDescriptionFr = DefaultUserTrackingDescriptionFr;
|
||||
UserTrackingUsageDescriptionJa = DefaultUserTrackingDescriptionJa;
|
||||
UserTrackingUsageDescriptionKo = DefaultUserTrackingDescriptionKo;
|
||||
UserTrackingUsageDescriptionZhHans = DefaultUserTrackingDescriptionZhHans;
|
||||
UserTrackingUsageDescriptionZhHant = DefaultUserTrackingDescriptionZhHant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to localize User Tracking Usage Description.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public bool UserTrackingUsageLocalizationEnabled
|
||||
{
|
||||
get { return userTrackingUsageLocalizationEnabled; }
|
||||
set
|
||||
{
|
||||
var previousValue = userTrackingUsageLocalizationEnabled;
|
||||
userTrackingUsageLocalizationEnabled = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
// If the value didn't change, don't do anything
|
||||
if (previousValue) return;
|
||||
|
||||
// Don't set the default values if they are being overriden.
|
||||
if (OverrideDefaultUserTrackingUsageDescriptions) return;
|
||||
|
||||
UserTrackingUsageDescriptionDe = DefaultUserTrackingDescriptionDe;
|
||||
UserTrackingUsageDescriptionEs = DefaultUserTrackingDescriptionEs;
|
||||
UserTrackingUsageDescriptionFr = DefaultUserTrackingDescriptionFr;
|
||||
UserTrackingUsageDescriptionJa = DefaultUserTrackingDescriptionJa;
|
||||
UserTrackingUsageDescriptionKo = DefaultUserTrackingDescriptionKo;
|
||||
UserTrackingUsageDescriptionZhHans = DefaultUserTrackingDescriptionZhHans;
|
||||
UserTrackingUsageDescriptionZhHant = DefaultUserTrackingDescriptionZhHant;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserTrackingUsageDescriptionDe = string.Empty;
|
||||
UserTrackingUsageDescriptionEs = string.Empty;
|
||||
UserTrackingUsageDescriptionFr = string.Empty;
|
||||
UserTrackingUsageDescriptionJa = string.Empty;
|
||||
UserTrackingUsageDescriptionKo = string.Empty;
|
||||
UserTrackingUsageDescriptionZhHans = string.Empty;
|
||||
UserTrackingUsageDescriptionZhHant = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in German to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionDe
|
||||
{
|
||||
get { return userTrackingUsageDescriptionDe; }
|
||||
set { userTrackingUsageDescriptionDe = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in Spanish to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionEs
|
||||
{
|
||||
get { return userTrackingUsageDescriptionEs; }
|
||||
set { userTrackingUsageDescriptionEs = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in French to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionFr
|
||||
{
|
||||
get { return userTrackingUsageDescriptionFr; }
|
||||
set { userTrackingUsageDescriptionFr = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in Japanese to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionJa
|
||||
{
|
||||
get { return userTrackingUsageDescriptionJa; }
|
||||
set { userTrackingUsageDescriptionJa = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in Korean to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionKo
|
||||
{
|
||||
get { return userTrackingUsageDescriptionKo; }
|
||||
set { userTrackingUsageDescriptionKo = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in Chinese (Simplified) to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionZhHans
|
||||
{
|
||||
get { return userTrackingUsageDescriptionZhHans; }
|
||||
set { userTrackingUsageDescriptionZhHans = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A User Tracking Usage Description in Chinese (Traditional) to be shown to users when requesting permission to use data for tracking.
|
||||
/// For more information see Apple's documentation: https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription
|
||||
/// </summary>
|
||||
public string UserTrackingUsageDescriptionZhHant
|
||||
{
|
||||
get { return userTrackingUsageDescriptionZhHant; }
|
||||
set { userTrackingUsageDescriptionZhHant = value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65c51e21887ae42c2839962fb9585e9f
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinInternalSettings.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// MaxMenuItems.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 5/27/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
public static class AppLovinMenuItems
|
||||
{
|
||||
/**
|
||||
* The special characters at the end represent a shortcut for this action.
|
||||
*
|
||||
* % - ctrl on Windows, cmd on macOS
|
||||
* # - shift
|
||||
* & - alt
|
||||
*
|
||||
* So, (shift + cmd/ctrl + i) will launch the integration manager
|
||||
*/
|
||||
[MenuItem("AppLovin/Integration Manager %#i")]
|
||||
private static void IntegrationManager()
|
||||
{
|
||||
ShowIntegrationManager();
|
||||
}
|
||||
|
||||
[MenuItem("AppLovin/Documentation")]
|
||||
private static void Documentation()
|
||||
{
|
||||
Application.OpenURL("https://support.axon.ai/en/max/unity/overview/integration");
|
||||
}
|
||||
|
||||
[MenuItem("AppLovin/Contact Us")]
|
||||
private static void ContactUs()
|
||||
{
|
||||
Application.OpenURL("https://www.applovin.com/contact/");
|
||||
}
|
||||
|
||||
[MenuItem("AppLovin/About")]
|
||||
private static void About()
|
||||
{
|
||||
Application.OpenURL("https://www.applovin.com/about/");
|
||||
}
|
||||
|
||||
[MenuItem("Assets/AppLovin Integration Manager")]
|
||||
private static void AssetsIntegrationManager()
|
||||
{
|
||||
ShowIntegrationManager();
|
||||
}
|
||||
|
||||
private static void ShowIntegrationManager()
|
||||
{
|
||||
AppLovinIntegrationManagerWindow.ShowManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02c2d277874f649d18a59d382420bf65
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinMenuItems.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,614 @@
|
||||
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";
|
||||
|
||||
private static readonly IPackageManagerClient _upmPackageManager = new AppLovinUpmPackageManager();
|
||||
private static readonly IPackageManagerClient _assetsPackageManager = new AppLovinAssetsPackageManager();
|
||||
|
||||
private static IPackageManagerClient PackageManagerClient
|
||||
{
|
||||
get
|
||||
{
|
||||
return AppLovinIntegrationManager.IsPluginInPackageManager ? _upmPackageManager : _assetsPackageManager;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PluginData PluginData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether or not an adapter with the given version or newer exists.
|
||||
/// </summary>
|
||||
/// <param name="adapterName">The name of the network (the root adapter folder name in "MaxSdk/Mediation/" folder.</param>
|
||||
/// <param name="iosVersion">The min iOS adapter version to check for. Can be <c>null</c> if we want to check for any version.</param>
|
||||
/// <param name="androidVersion">The min android adapter version to check for. Can be <c>null</c> if we want to check for any version.</param>
|
||||
/// <returns><c>true</c> if an adapter with the min version is installed.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an adapter is installed using the plugin data.
|
||||
/// </summary>
|
||||
/// <param name="pluginData">The plugin data to check for the adapter</param>
|
||||
/// <param name="adapterName">The name of the network.</param>
|
||||
/// <returns>Whether an adapter is installed in the plugin data</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>A list of the installed mediation network names.</returns>
|
||||
internal static List<string> GetInstalledMediationNetworks()
|
||||
{
|
||||
var installedNetworks = new List<string>();
|
||||
var installedNetworksInAssets = AppLovinAssetsPackageManager.GetInstalledMediationNetworks();
|
||||
installedNetworks.AddRange(installedNetworksInAssets);
|
||||
|
||||
var installedNetworksInPackages = AppLovinUpmPackageManager.GetInstalledMediationNetworks();
|
||||
installedNetworks.AddRange(installedNetworksInPackages);
|
||||
|
||||
if (IsAmazonAppLovinAdapterInstalled())
|
||||
{
|
||||
installedNetworks.Add("AmazonAdMarketplace");
|
||||
}
|
||||
|
||||
return installedNetworks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a network to the project.
|
||||
/// </summary>
|
||||
/// <param name="network">The network to add.</param>
|
||||
/// <param name="showImport">Whether to show the import window (only for non UPM)</param>
|
||||
internal static IEnumerator AddNetwork(Network network, bool showImport)
|
||||
{
|
||||
yield return PackageManagerClient.AddNetwork(network, showImport);
|
||||
|
||||
AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a network from the project.
|
||||
/// </summary>
|
||||
/// <param name="network">The network to remove.</param>
|
||||
internal static void RemoveNetwork(Network network)
|
||||
{
|
||||
PackageManagerClient.RemoveNetwork(network);
|
||||
|
||||
AppLovinEditorCoroutine.StartCoroutine(RefreshAssetsAtEndOfFrame(network));
|
||||
}
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all asset paths 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 an empty list if the asset is not found.</returns>
|
||||
private static List<string> GetAssetPathListForExportPath(string exportPath)
|
||||
{
|
||||
var assetLabelToFind = "l:al_max_export_path-" + MaxSdkUtils.NormalizeToUnityPath(exportPath);
|
||||
var assetGuids = AssetDatabase.FindAssets(assetLabelToFind);
|
||||
|
||||
var assetPaths = new List<string>();
|
||||
foreach (var assetGuid in assetGuids)
|
||||
{
|
||||
assetPaths.Add(AssetDatabase.GUIDToAssetPath(assetGuid));
|
||||
}
|
||||
|
||||
return assetPaths.Count <= 0 ? new List<string>() : assetPaths;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the CurrentVersion fields for a given network data object.
|
||||
/// </summary>
|
||||
/// <param name="network">Network for which to update the current versions.</param>
|
||||
internal static void UpdateCurrentVersions(Network network)
|
||||
{
|
||||
var assetPaths = GetAssetPathListForExportPath(network.DependenciesFilePath);
|
||||
if (HasDuplicateAdapters(assetPaths))
|
||||
{
|
||||
ShowDeleteDuplicateAdapterPrompt(network);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a network has duplicate adapters installed in both the Assets folder and via UPM.
|
||||
/// </summary>
|
||||
/// <param name="dependencyPaths">The list of paths to the dependencies.xml files</param>
|
||||
/// <returns><c>True</c> if there are adapters in both the Assets folder and installed via UPM</returns>
|
||||
private static bool HasDuplicateAdapters(List<string> dependencyPaths)
|
||||
{
|
||||
var inPackagesFolder = dependencyPaths.Any(path => path.Contains("Packages"));
|
||||
var inAssetsFolder = dependencyPaths.Any(path => path.Contains("Assets"));
|
||||
|
||||
return inPackagesFolder && inAssetsFolder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a prompt informing the user that duplicate adapters were detected
|
||||
/// and allows them to choose which version to keep.
|
||||
/// </summary>
|
||||
/// <param name="network">The network that has duplicate adapters installed.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a duplicate adapter by either deleting it from the Assets folder
|
||||
/// or uninstalling it from the Unity Package Manager (UPM).
|
||||
/// </summary>
|
||||
/// <param name="network">The network for which the duplicate adapter is being removed.</param>
|
||||
/// <param name="keepAssetsAdapter">If <c>true</c>, retains the adapter in the Assets folder and removes the UPM version;
|
||||
/// otherwise, deletes the adapter from the Assets folder.</param>
|
||||
internal static void DeleteDuplicateAdapter(Network network, bool keepAssetsAdapter)
|
||||
{
|
||||
// Skip duplicate removal logic for our plugin.
|
||||
if (network.Name.Equals("APPLOVIN_NETWORK")) return;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="dependencyPaths">A list of dependency file paths to extract current versions from.</param>
|
||||
/// <returns>Current versions of a given network's dependency files.</returns>
|
||||
private static Versions GetCurrentVersions(List<string> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the current version of a network from its dependency.xml file.
|
||||
/// </summary>
|
||||
/// <param name="currentVersions">The Versions object we are using.</param>
|
||||
/// <param name="dependencyPath">The path to the dependency.xml file.</param>
|
||||
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;
|
||||
}
|
||||
|
||||
// <dependencies>
|
||||
// <androidPackages>
|
||||
// <androidPackage spec="com.applovin.mediation:network_name-adapter:1.2.3.4" />
|
||||
// </androidPackages>
|
||||
// <iosPods>
|
||||
// <iosPod name="AppLovinMediationNetworkNameAdapter" version="2.3.4.5" />
|
||||
// </iosPods>
|
||||
// </dependencies>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check for the Amazon AppLovin adapter in the project.
|
||||
/// </summary>
|
||||
/// <returns>Whether the AppLovin Adapter is installed through the Amazon SDK.</returns>
|
||||
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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refresh assets and update current versions after a slight delay to allow for Client.Resolve to finish.
|
||||
/// </summary>
|
||||
/// <param name="network">The network that was just installed/removed.</param>
|
||||
private static IEnumerator RefreshAssetsAtEndOfFrame(Network network)
|
||||
{
|
||||
yield return new WaitForEndOfFrame();
|
||||
UpdateCurrentVersions(network);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
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<string> GetInstalledMediationNetworks()
|
||||
{
|
||||
// Return empty list if we failed to get the package list
|
||||
var packageCollection = GetPackageCollectionSync(TimeoutFetchPackageCollectionSeconds);
|
||||
if (packageCollection == null)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a network's packages to the package manager removes any beta version that exists
|
||||
/// </summary>
|
||||
/// <param name="network">The network to add.</param>
|
||||
/// <param name="appLovinManifest">The AppLovinUpmManifest instance to edit</param>
|
||||
internal static void AddPackages(Network network, AppLovinUpmManifest appLovinManifest)
|
||||
{
|
||||
foreach (var packageInfo in network.Packages)
|
||||
{
|
||||
appLovinManifest.AddPackageDependency(packageInfo.Name, packageInfo.Version);
|
||||
RemoveBetaPackage(packageInfo.Name, appLovinManifest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a network's packages from the package manager
|
||||
/// </summary>
|
||||
/// <param name="network">The network to add.</param>
|
||||
/// <param name="appLovinManifest">The AppLovinUpmManifest instance to edit</param>
|
||||
internal static void RemovePackages(Network network, AppLovinUpmManifest appLovinManifest)
|
||||
{
|
||||
foreach (var packageInfo in network.Packages)
|
||||
{
|
||||
appLovinManifest.RemovePackageDependency(packageInfo.Name);
|
||||
RemoveBetaPackage(packageInfo.Name, appLovinManifest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the beta version of a package name
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the package to remove a beta for</param>
|
||||
/// <param name="appLovinManifest">The AppLovinUpmManifest instance to edit</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the Unity Package Manager so any changes made to the manifest.json file are reflected in the Unity Editor.
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PackageCollection from the Unity Package Manager synchronously.
|
||||
/// </summary>
|
||||
/// <param name="timeoutSeconds">How long to wait before exiting with a timeout error</param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public class AppLovinAssetsPackageManager : IPackageManagerClient
|
||||
{
|
||||
public static List<string> GetInstalledMediationNetworks()
|
||||
{
|
||||
var maxMediationDirectory = AppLovinIntegrationManager.MediationDirectory;
|
||||
if (!Directory.Exists(maxMediationDirectory)) return new List<string>();
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69faa9dfd9aac483daa24261a3e11206
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPackageManager.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,166 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Moves our SDK Unity Plugin from under the Assets folder to the Unity Package Manager.
|
||||
/// </summary>
|
||||
public static class AppLovinPluginMigrationHelper
|
||||
{
|
||||
private const string ApplovinRegistryName = "AppLovin MAX Unity";
|
||||
private const string ApplovinRegistryUrl = "https://unity.packages.applovin.com/";
|
||||
private static readonly List<string> AppLovinRegistryScopes = new List<string>() {"com.applovin.mediation.ads", "com.applovin.mediation.adapters", "com.applovin.mediation.dsp"};
|
||||
|
||||
private const string OpenUpmRegistryName = "package.openupm.com";
|
||||
private const string OpenUpmRegistryUrl = "https://package.openupm.com";
|
||||
private static readonly List<string> OpenUpmRegistryScopes = new List<string>() {"com.google.external-dependency-manager"};
|
||||
|
||||
private static List<string> betaNetworkPluginFilePaths = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to move the Unity plugin to UPM by adding the AppLovin scoped registry and dependencies to the manifest.
|
||||
/// </summary>
|
||||
/// <param name="pluginData">The Unity Plugin data for our sdk and mediation adapters.</param>
|
||||
/// <param name="deleteExternalDependencyManager">Whether to delete the EDM folder under "Assets"</param>
|
||||
internal static void MigrateToUnityPackageManager(PluginData pluginData, bool deleteExternalDependencyManager)
|
||||
{
|
||||
MaxSdkLogger.UserDebug("Moving AppLovin Unity Plugin to package manager");
|
||||
|
||||
if (deleteExternalDependencyManager)
|
||||
{
|
||||
DeleteExternalDependencyManager();
|
||||
}
|
||||
|
||||
var appLovinManifest = AppLovinUpmManifest.Load();
|
||||
|
||||
MigrateAdapters(pluginData, appLovinManifest);
|
||||
MigratePlugin(pluginData, appLovinManifest);
|
||||
|
||||
appLovinManifest.Save();
|
||||
AppLovinUpmPackageManager.ResolvePackageManager();
|
||||
DeletePluginFiles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add all currently installed networks to the manifest.
|
||||
/// </summary>
|
||||
internal static void MigrateAdapters(PluginData pluginData, AppLovinUpmManifest appLovinManifest)
|
||||
{
|
||||
var allNetworks = pluginData.MediatedNetworks.Concat(pluginData.PartnerMicroSdks).ToArray();
|
||||
betaNetworkPluginFilePaths.Clear();
|
||||
|
||||
// Add every currently installed network and separate it by android and iOS.
|
||||
foreach (var network in allNetworks)
|
||||
{
|
||||
var currentVersion = network.CurrentVersions != null ? network.CurrentVersions.Unity : "";
|
||||
if (string.IsNullOrEmpty(currentVersion)) continue;
|
||||
|
||||
if (currentVersion.Contains("beta"))
|
||||
{
|
||||
betaNetworkPluginFilePaths.AddRange(network.PluginFilePaths);
|
||||
continue;
|
||||
}
|
||||
|
||||
AppLovinUpmPackageManager.AddPackages(network, appLovinManifest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the AppLovin scoped registry to the manifest if it doesn't exist. Otherwise update it.
|
||||
/// </summary>
|
||||
private static void MigratePlugin(PluginData pluginData, AppLovinUpmManifest appLovinManifest)
|
||||
{
|
||||
appLovinManifest.AddOrUpdateRegistry(ApplovinRegistryName, ApplovinRegistryUrl, AppLovinRegistryScopes);
|
||||
appLovinManifest.AddOrUpdateRegistry(OpenUpmRegistryName, OpenUpmRegistryUrl, OpenUpmRegistryScopes);
|
||||
|
||||
var appLovinVersion = pluginData.AppLovinMax.LatestVersions.Unity;
|
||||
appLovinManifest.AddPackageDependency(AppLovinUpmPackageManager.PackageNamePrefixAppLovin, appLovinVersion);
|
||||
}
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Delete the external dependency manager folder from the project.
|
||||
/// </summary>
|
||||
private static void DeleteExternalDependencyManager()
|
||||
{
|
||||
var externalDependencyManagerPath = Path.Combine(Application.dataPath, "ExternalDependencyManager");
|
||||
FileUtil.DeleteFileOrDirectory(externalDependencyManagerPath);
|
||||
FileUtil.DeleteFileOrDirectory(externalDependencyManagerPath + ".meta");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all the files in the plugin directory except the AppLovinSettings.asset file.
|
||||
/// </summary>
|
||||
private static void DeletePluginFiles()
|
||||
{
|
||||
if (AppLovinIntegrationManager.IsPluginInPackageManager) return;
|
||||
|
||||
var pluginPath = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, "MaxSdk");
|
||||
var appLovinSettingsPath = Path.Combine(pluginPath, "Resources/AppLovinSettings.asset");
|
||||
|
||||
var appLovinResourcesDirectory = Path.Combine(pluginPath, "Resources");
|
||||
var appLovinSettingsTempPath = Path.Combine(Path.GetTempPath(), "AppLovinSettings.asset");
|
||||
var appLovinMediationTempPath = Path.Combine(Path.GetTempPath(), "Mediation");
|
||||
|
||||
// Ensure there aren't any errors when moving the files due to the directories/files already existing.
|
||||
FileUtil.DeleteFileOrDirectory(appLovinSettingsTempPath);
|
||||
FileUtil.DeleteFileOrDirectory(appLovinMediationTempPath);
|
||||
|
||||
var mediationAssetsDir = Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, "MaxSdk/Mediation");
|
||||
|
||||
// Move the AppLovinSettings.asset file and any beta adapters to a temporary directory.
|
||||
File.Move(appLovinSettingsPath, appLovinSettingsTempPath);
|
||||
var adapterSaved = MoveBetaAdaptersIfNeeded(mediationAssetsDir, appLovinMediationTempPath);
|
||||
|
||||
// Move the meta file if the adapter was saved to save the asset labels
|
||||
if (adapterSaved)
|
||||
{
|
||||
File.Move(mediationAssetsDir, appLovinMediationTempPath + ".meta");
|
||||
}
|
||||
|
||||
// Delete the plugin directory and then move the AppLovinSettings.asset and beta adapters back.
|
||||
FileUtil.DeleteFileOrDirectory(pluginPath);
|
||||
Directory.CreateDirectory(appLovinResourcesDirectory);
|
||||
File.Move(appLovinSettingsTempPath, appLovinSettingsPath);
|
||||
MoveBetaAdaptersIfNeeded(appLovinMediationTempPath, mediationAssetsDir);
|
||||
if (adapterSaved)
|
||||
{
|
||||
File.Move(appLovinMediationTempPath + ".meta", mediationAssetsDir);
|
||||
}
|
||||
|
||||
FileUtil.DeleteFileOrDirectory(appLovinMediationTempPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the beta adapters from a source mediation directory to a destination mediation directory.
|
||||
/// </summary>
|
||||
/// <param name="sourceMediationDirectory">The directory containing the beta adapters to be moved.</param>
|
||||
/// <param name="destinationMediationDirectory">The target directory where the beta adapters should be moved.</param>
|
||||
private static bool MoveBetaAdaptersIfNeeded(string sourceMediationDirectory, string destinationMediationDirectory)
|
||||
{
|
||||
if (betaNetworkPluginFilePaths.Count == 0 || !Directory.Exists(sourceMediationDirectory)) return false;
|
||||
|
||||
var movedAdapter = false;
|
||||
Directory.CreateDirectory(destinationMediationDirectory);
|
||||
foreach (var pluginFilePath in betaNetworkPluginFilePaths)
|
||||
{
|
||||
var sourceDirectory = Path.Combine(sourceMediationDirectory, Path.GetFileName(pluginFilePath));
|
||||
var destinationDirectory = Path.Combine(destinationMediationDirectory, Path.GetFileName(pluginFilePath));
|
||||
if (Directory.Exists(sourceDirectory))
|
||||
{
|
||||
Directory.Move(sourceDirectory, destinationDirectory);
|
||||
movedAdapter = true;
|
||||
}
|
||||
}
|
||||
|
||||
return movedAdapter;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11288612632de49b99708cdee436692c
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPluginMigrationHelper.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,482 @@
|
||||
//
|
||||
// MaxPostProcessBuildAndroid.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 4/10/20.
|
||||
// Copyright © 2020 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_ANDROID
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Xml.Linq;
|
||||
using AppLovinMax.ThirdParty.MiniJson;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Android;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// A post processor used to update the Android project once it is generated.
|
||||
/// </summary>
|
||||
public class AppLovinPostProcessAndroid : IPostGenerateGradleAndroidProject
|
||||
{
|
||||
private const string PropertyAndroidX = "android.useAndroidX";
|
||||
private const string PropertyJetifier = "android.enableJetifier";
|
||||
private const string EnableProperty = "=true";
|
||||
private const string PropertyDexingArtifactTransform = "android.enableDexingArtifactTransform";
|
||||
private const string DisableProperty = "=false";
|
||||
|
||||
private const string KeyMetaDataAppLovinVerboseLoggingOn = "applovin.sdk.verbose_logging";
|
||||
private const string KeyMetaDataGoogleApplicationId = "com.google.android.gms.ads.APPLICATION_ID";
|
||||
private const string KeyMetaDataGoogleOptimizeInitialization = "com.google.android.gms.ads.flag.OPTIMIZE_INITIALIZATION";
|
||||
private const string KeyMetaDataGoogleOptimizeAdLoading = "com.google.android.gms.ads.flag.OPTIMIZE_AD_LOADING";
|
||||
|
||||
private const string KeyMetaDataMobileFuseAutoInit = "com.mobilefuse.sdk.disable_auto_init";
|
||||
private const string KeyMetaDataMyTargetAutoInit = "com.my.target.autoInitMode";
|
||||
|
||||
private const string KeyMetaDataAppLovinSdkKey = "applovin.sdk.key";
|
||||
|
||||
private const string AppLovinSettingsFileName = "applovin_settings.json";
|
||||
|
||||
private const string KeySdkKey = "sdk_key";
|
||||
private const string KeyConsentFlowSettings = "consent_flow_settings";
|
||||
private const string KeyConsentFlowEnabled = "consent_flow_enabled";
|
||||
private const string KeyConsentFlowTermsOfService = "consent_flow_terms_of_service";
|
||||
private const string KeyConsentFlowPrivacyPolicy = "consent_flow_privacy_policy";
|
||||
private const string KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR = "consent_flow_show_terms_and_privacy_policy_alert_in_gdpr";
|
||||
private const string KeyConsentFlowDebugUserGeography = "consent_flow_debug_user_geography";
|
||||
|
||||
private const string KeyRenderOutsideSafeArea = "render_outside_safe_area";
|
||||
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
// To match "'com.android.library' version '7.3.1'" line in build.gradle
|
||||
private static readonly Regex TokenGradleVersionLibrary = new Regex(".*id ['\"]com\\.android\\.library['\"] version");
|
||||
private static readonly Regex TokenGradleVersion = new Regex(".*id ['\"]com\\.android\\.application['\"] version");
|
||||
#else
|
||||
// To match "classpath 'com.android.tools.build:gradle:4.0.1'" line in build.gradle
|
||||
private static readonly Regex TokenGradleVersion = new Regex(".*classpath ['\"]com\\.android\\.tools\\.build:gradle:.*");
|
||||
#endif
|
||||
|
||||
// To match "distributionUrl=..." in gradle-wrapper.properties file
|
||||
private static readonly Regex TokenDistributionUrl = new Regex(".*distributionUrl.*");
|
||||
|
||||
private static readonly XNamespace AndroidNamespace = "http://schemas.android.com/apk/res/android";
|
||||
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
var rootGradleBuildFilePath = Path.Combine(path, "../build.gradle");
|
||||
var gradlePropertiesPath = Path.Combine(path, "../gradle.properties");
|
||||
var gradleWrapperPropertiesPath = Path.Combine(path, "../gradle/wrapper/gradle-wrapper.properties");
|
||||
|
||||
UpdateGradleVersionsIfNeeded(gradleWrapperPropertiesPath, rootGradleBuildFilePath);
|
||||
|
||||
var gradlePropertiesUpdated = new List<string>();
|
||||
|
||||
// If the gradle properties file already exists, make sure to add any previous properties.
|
||||
if (File.Exists(gradlePropertiesPath))
|
||||
{
|
||||
var lines = File.ReadAllLines(gradlePropertiesPath);
|
||||
|
||||
// Add all properties except AndroidX, Jetifier, and DexingArtifactTransform since they may already exist. We will re-add them below.
|
||||
gradlePropertiesUpdated.AddRange(lines.Where(line => !line.Contains(PropertyAndroidX) && !line.Contains(PropertyJetifier) && !line.Contains(PropertyDexingArtifactTransform)));
|
||||
}
|
||||
|
||||
// Enable AndroidX and Jetifier properties
|
||||
gradlePropertiesUpdated.Add(PropertyAndroidX + EnableProperty);
|
||||
gradlePropertiesUpdated.Add(PropertyJetifier + EnableProperty);
|
||||
|
||||
// `DexingArtifactTransform` has been removed in Gradle 8+ which is the default Gradle version for Unity 6.
|
||||
#if !UNITY_6000_0_OR_NEWER
|
||||
// Disable dexing using artifact transform (it causes issues for ExoPlayer with Gradle plugin 3.5.0+)
|
||||
gradlePropertiesUpdated.Add(PropertyDexingArtifactTransform + DisableProperty);
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(gradlePropertiesPath, string.Join("\n", gradlePropertiesUpdated.ToArray()) + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to enable AndroidX and Jetifier. gradle.properties file write failed.");
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
|
||||
ProcessAndroidManifest(path);
|
||||
AddSdkSettings(path);
|
||||
}
|
||||
|
||||
public int callbackOrder
|
||||
{
|
||||
get { return AppLovinPreProcess.CallbackOrder; }
|
||||
}
|
||||
|
||||
private static void ProcessAndroidManifest(string path)
|
||||
{
|
||||
var manifestPath = Path.Combine(path, "src/main/AndroidManifest.xml");
|
||||
XDocument manifest;
|
||||
try
|
||||
{
|
||||
manifest = XDocument.Load(manifestPath);
|
||||
}
|
||||
#pragma warning disable 0168
|
||||
catch (IOException exception)
|
||||
#pragma warning restore 0168
|
||||
{
|
||||
MaxSdkLogger.UserWarning("[AppLovin MAX] AndroidManifest.xml is missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the `manifest` element.
|
||||
var elementManifest = manifest.Element("manifest");
|
||||
if (elementManifest == null)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("[AppLovin MAX] AndroidManifest.xml is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var elementApplication = elementManifest.Element("application");
|
||||
if (elementApplication == null)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("[AppLovin MAX] AndroidManifest.xml is invalid.");
|
||||
return;
|
||||
}
|
||||
|
||||
var metaDataElements = elementApplication.Descendants().Where(element => element.Name.LocalName.Equals("meta-data"));
|
||||
|
||||
EnableVerboseLoggingIfNeeded(elementApplication);
|
||||
AddGoogleApplicationIdIfNeeded(elementApplication, metaDataElements);
|
||||
AddGoogleOptimizationFlagsIfNeeded(elementApplication, metaDataElements);
|
||||
DisableAutoInitIfNeeded(elementApplication, metaDataElements);
|
||||
RemoveSdkKeyIfNeeded(metaDataElements);
|
||||
|
||||
// Save the updated manifest file.
|
||||
manifest.Save(manifestPath);
|
||||
}
|
||||
|
||||
private static void EnableVerboseLoggingIfNeeded(XElement elementApplication)
|
||||
{
|
||||
var enabled = EditorPrefs.GetBool(MaxSdkLogger.KeyVerboseLoggingEnabled, false);
|
||||
|
||||
var descendants = elementApplication.Descendants();
|
||||
var verboseLoggingMetaData = descendants.FirstOrDefault(descendant => descendant.FirstAttribute != null &&
|
||||
descendant.FirstAttribute.Name.LocalName.Equals("name") &&
|
||||
descendant.FirstAttribute.Value.Equals(KeyMetaDataAppLovinVerboseLoggingOn) &&
|
||||
descendant.LastAttribute != null &&
|
||||
descendant.LastAttribute.Name.LocalName.Equals("value"));
|
||||
|
||||
// check if applovin.sdk.verbose_logging meta data exists.
|
||||
if (verboseLoggingMetaData != null)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
// update applovin.sdk.verbose_logging meta data value.
|
||||
verboseLoggingMetaData.LastAttribute.Value = enabled.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
// remove applovin.sdk.verbose_logging meta data.
|
||||
verboseLoggingMetaData.Remove();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
// add applovin.sdk.verbose_logging meta data if it does not exist.
|
||||
var metaData = CreateMetaDataElement(KeyMetaDataAppLovinVerboseLoggingOn, enabled.ToString());
|
||||
elementApplication.Add(metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddGoogleApplicationIdIfNeeded(XElement elementApplication, IEnumerable<XElement> metaDataElements)
|
||||
{
|
||||
if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
|
||||
|
||||
var googleApplicationIdMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataGoogleApplicationId);
|
||||
var appId = AppLovinSettings.Instance.AdMobAndroidAppId;
|
||||
// Log error if the App ID is not set.
|
||||
if (string.IsNullOrEmpty(appId) || !appId.StartsWith("ca-app-pub-"))
|
||||
{
|
||||
MaxSdkLogger.UserError("Google App ID is not set. Please enter a valid app ID within the AppLovin Integration Manager window.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the Google App ID meta data already exists. Update if it already exists.
|
||||
if (googleApplicationIdMetaData != null)
|
||||
{
|
||||
googleApplicationIdMetaData.SetAttributeValue(AndroidNamespace + "value", appId);
|
||||
}
|
||||
// Meta data doesn't exist, add it.
|
||||
else
|
||||
{
|
||||
elementApplication.Add(CreateMetaDataElement(KeyMetaDataGoogleApplicationId, appId));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddGoogleOptimizationFlagsIfNeeded(XElement elementApplication, IEnumerable<XElement> metaDataElements)
|
||||
{
|
||||
if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
|
||||
|
||||
var googleOptimizeInitializationMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataGoogleOptimizeInitialization);
|
||||
// If meta data doesn't exist, add it
|
||||
if (googleOptimizeInitializationMetaData == null)
|
||||
{
|
||||
elementApplication.Add(CreateMetaDataElement(KeyMetaDataGoogleOptimizeInitialization, true));
|
||||
}
|
||||
|
||||
var googleOptimizeAdLoadingMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataGoogleOptimizeAdLoading);
|
||||
// If meta data doesn't exist, add it
|
||||
if (googleOptimizeAdLoadingMetaData == null)
|
||||
{
|
||||
elementApplication.Add(CreateMetaDataElement(KeyMetaDataGoogleOptimizeAdLoading, true));
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisableAutoInitIfNeeded(XElement elementApplication, IEnumerable<XElement> metaDataElements)
|
||||
{
|
||||
if (AppLovinPackageManager.IsAdapterInstalled("MobileFuse"))
|
||||
{
|
||||
var mobileFuseMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataMobileFuseAutoInit);
|
||||
// If MobileFuse meta data doesn't exist, add it
|
||||
if (mobileFuseMetaData == null)
|
||||
{
|
||||
elementApplication.Add(CreateMetaDataElement(KeyMetaDataMobileFuseAutoInit, true));
|
||||
}
|
||||
}
|
||||
|
||||
if (AppLovinPackageManager.IsAdapterInstalled("MyTarget"))
|
||||
{
|
||||
var myTargetMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataMyTargetAutoInit);
|
||||
// If MyTarget meta data doesn't exist, add it
|
||||
if (myTargetMetaData == null)
|
||||
{
|
||||
elementApplication.Add(CreateMetaDataElement(KeyMetaDataMyTargetAutoInit, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveSdkKeyIfNeeded(IEnumerable<XElement> metaDataElements)
|
||||
{
|
||||
var sdkKeyMetaData = GetMetaDataElement(metaDataElements, KeyMetaDataAppLovinSdkKey);
|
||||
if (sdkKeyMetaData == null) return;
|
||||
|
||||
sdkKeyMetaData.Remove();
|
||||
}
|
||||
|
||||
private static void UpdateGradleVersionsIfNeeded(string gradleWrapperPropertiesPath, string rootGradleBuildFilePath)
|
||||
{
|
||||
var customGradleVersionUrl = AppLovinSettings.Instance.CustomGradleVersionUrl;
|
||||
var customGradleToolsVersion = AppLovinSettings.Instance.CustomGradleToolsVersion;
|
||||
|
||||
if (MaxSdkUtils.IsValidString(customGradleVersionUrl))
|
||||
{
|
||||
var newDistributionUrl = string.Format("distributionUrl={0}", customGradleVersionUrl);
|
||||
if (ReplaceStringInFile(gradleWrapperPropertiesPath, TokenDistributionUrl, newDistributionUrl))
|
||||
{
|
||||
MaxSdkLogger.D("Distribution url set to " + newDistributionUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSdkLogger.E("Failed to set distribution URL");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaxSdkUtils.IsValidString(customGradleToolsVersion))
|
||||
{
|
||||
#if UNITY_2022_3_OR_NEWER
|
||||
// Unity 2022.3+ requires Gradle Plugin version 7.1.2+.
|
||||
if (MaxSdkUtils.CompareVersions(customGradleToolsVersion, "7.1.2") == MaxSdkUtils.VersionComparisonResult.Lesser)
|
||||
{
|
||||
MaxSdkLogger.E("Failed to set gradle plugin version. Unity 2022.3+ requires gradle plugin version 7.1.2+");
|
||||
return;
|
||||
}
|
||||
|
||||
var newGradleVersionLibraryLine = AppLovinProcessGradleBuildFile.GetFormattedBuildScriptLine(string.Format("id 'com.android.library' version '{0}' apply false", customGradleToolsVersion));
|
||||
if (ReplaceStringInFile(rootGradleBuildFilePath, TokenGradleVersionLibrary, newGradleVersionLibraryLine))
|
||||
{
|
||||
MaxSdkLogger.D("Gradle library version set to " + newGradleVersionLibraryLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSdkLogger.E("Failed to set gradle library version");
|
||||
}
|
||||
|
||||
var newGradleVersionLine = AppLovinProcessGradleBuildFile.GetFormattedBuildScriptLine(string.Format("id 'com.android.application' version '{0}' apply false", customGradleToolsVersion));
|
||||
#else
|
||||
var newGradleVersionLine = AppLovinProcessGradleBuildFile.GetFormattedBuildScriptLine(string.Format("classpath 'com.android.tools.build:gradle:{0}'", customGradleToolsVersion));
|
||||
#endif
|
||||
if (ReplaceStringInFile(rootGradleBuildFilePath, TokenGradleVersion, newGradleVersionLine))
|
||||
{
|
||||
MaxSdkLogger.D("Gradle version set to " + newGradleVersionLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSdkLogger.E("Failed to set gradle plugin version");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddSdkSettings(string path)
|
||||
{
|
||||
var appLovinSdkSettings = new Dictionary<string, object>();
|
||||
var rawResourceDirectory = Path.Combine(path, "src/main/res/raw");
|
||||
|
||||
// Add the SDK key to the SDK settings.
|
||||
appLovinSdkSettings[KeySdkKey] = AppLovinSettings.Instance.SdkKey;
|
||||
appLovinSdkSettings[KeyRenderOutsideSafeArea] = PlayerSettings.Android.renderOutsideSafeArea;
|
||||
|
||||
// Add the Terms and Privacy Policy flow settings if needed.
|
||||
EnableConsentFlowIfNeeded(rawResourceDirectory, appLovinSdkSettings);
|
||||
|
||||
WriteAppLovinSettings(rawResourceDirectory, appLovinSdkSettings);
|
||||
}
|
||||
|
||||
private static void EnableConsentFlowIfNeeded(string rawResourceDirectory, Dictionary<string, object> applovinSdkSettings)
|
||||
{
|
||||
// Check if consent flow is enabled. No need to create the applovin_consent_flow_settings.json if consent flow is disabled.
|
||||
var consentFlowEnabled = AppLovinInternalSettings.Instance.ConsentFlowEnabled;
|
||||
if (!consentFlowEnabled)
|
||||
{
|
||||
RemoveAppLovinSettingsRawResourceFileIfNeeded(rawResourceDirectory);
|
||||
return;
|
||||
}
|
||||
|
||||
var privacyPolicyUrl = AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl;
|
||||
if (string.IsNullOrEmpty(privacyPolicyUrl))
|
||||
{
|
||||
AppLovinIntegrationManager.ShowBuildFailureDialog("You cannot use the AppLovin SDK's consent flow without defining a Privacy Policy URL in the AppLovin Integration Manager.");
|
||||
|
||||
// No need to update the applovin_consent_flow_settings.json here. Default consent flow state will be determined on the SDK side.
|
||||
return;
|
||||
}
|
||||
|
||||
var consentFlowSettings = new Dictionary<string, object>();
|
||||
consentFlowSettings[KeyConsentFlowEnabled] = consentFlowEnabled;
|
||||
consentFlowSettings[KeyConsentFlowPrivacyPolicy] = privacyPolicyUrl;
|
||||
|
||||
var termsOfServiceUrl = AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl;
|
||||
if (MaxSdkUtils.IsValidString(termsOfServiceUrl))
|
||||
{
|
||||
consentFlowSettings[KeyConsentFlowTermsOfService] = termsOfServiceUrl;
|
||||
}
|
||||
|
||||
consentFlowSettings[KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR] = AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR;
|
||||
|
||||
var debugUserGeography = AppLovinInternalSettings.Instance.DebugUserGeography;
|
||||
if (debugUserGeography == MaxSdkBase.ConsentFlowUserGeography.Gdpr)
|
||||
{
|
||||
consentFlowSettings[KeyConsentFlowDebugUserGeography] = "gdpr";
|
||||
}
|
||||
|
||||
applovinSdkSettings[KeyConsentFlowSettings] = consentFlowSettings;
|
||||
}
|
||||
|
||||
private static void WriteAppLovinSettingsRawResourceFile(string applovinSdkSettingsJson, string rawResourceDirectory)
|
||||
{
|
||||
if (!Directory.Exists(rawResourceDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(rawResourceDirectory);
|
||||
}
|
||||
|
||||
var consentFlowSettingsFilePath = Path.Combine(rawResourceDirectory, AppLovinSettingsFileName);
|
||||
try
|
||||
{
|
||||
File.WriteAllText(consentFlowSettingsFilePath, applovinSdkSettingsJson + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("applovin_settings.json file write failed due to: " + exception.Message);
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the applovin_settings json file from the build if it exists.
|
||||
/// </summary>
|
||||
/// <param name="rawResourceDirectory">The raw resource directory that holds the json file</param>
|
||||
private static void RemoveAppLovinSettingsRawResourceFileIfNeeded(string rawResourceDirectory)
|
||||
{
|
||||
var consentFlowSettingsFilePath = Path.Combine(rawResourceDirectory, AppLovinSettingsFileName);
|
||||
if (!File.Exists(consentFlowSettingsFilePath)) return;
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(consentFlowSettingsFilePath);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Deleting applovin_settings.json failed due to: " + exception.Message);
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteAppLovinSettings(string rawResourceDirectory, Dictionary<string, object> applovinSdkSettings)
|
||||
{
|
||||
var applovinSdkSettingsJson = Json.Serialize(applovinSdkSettings);
|
||||
WriteAppLovinSettingsRawResourceFile(applovinSdkSettingsJson, rawResourceDirectory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a <c>meta-data</c> element with the given name and value.
|
||||
/// </summary>
|
||||
private static XElement CreateMetaDataElement(string name, object value)
|
||||
{
|
||||
var metaData = new XElement("meta-data");
|
||||
metaData.Add(new XAttribute(AndroidNamespace + "name", name));
|
||||
metaData.Add(new XAttribute(AndroidNamespace + "value", value));
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks through all the given meta-data elements to check if the required one exists. Returns <c>null</c> if it doesn't exist.
|
||||
/// </summary>
|
||||
private static XElement GetMetaDataElement(IEnumerable<XElement> metaDataElements, string metaDataName)
|
||||
{
|
||||
foreach (var metaDataElement in metaDataElements)
|
||||
{
|
||||
var attributes = metaDataElement.Attributes();
|
||||
if (attributes.Any(attribute => attribute.Name.Namespace.Equals(AndroidNamespace)
|
||||
&& attribute.Name.LocalName.Equals("name")
|
||||
&& attribute.Value.Equals(metaDataName)))
|
||||
{
|
||||
return metaDataElement;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the first line that contains regexToMatch and replaces the whole line with replacement
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the file you want to replace a line in</param>
|
||||
/// <param name="regexToMatch">Regex to search for in the line you want to replace</param>
|
||||
/// <param name="replacement">String that you want as the new line</param>
|
||||
/// <returns>Returns whether the string was successfully replaced or not</returns>
|
||||
private static bool ReplaceStringInFile(string path, Regex regexToMatch, string replacement)
|
||||
{
|
||||
if (!File.Exists(path)) return false;
|
||||
|
||||
var lines = File.ReadAllLines(path);
|
||||
for (var i = 0; i < lines.Length; i++)
|
||||
{
|
||||
if (regexToMatch.IsMatch(lines[i]))
|
||||
{
|
||||
lines[i] = replacement;
|
||||
File.WriteAllLines(path, lines);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c01ea79d0cb2a43c093e2fd07201df9e
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPostProcessAndroid.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// AppLovinBuildPostProcessor.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 8/29/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_ANDROID
|
||||
|
||||
using System.IO;
|
||||
using UnityEditor.Android;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Quality Service plugin to the Gradle project once the project has been exported. See <see cref="AppLovinProcessGradleBuildFile"/> for more details.
|
||||
/// </summary>
|
||||
public class AppLovinPostProcessGradleProject : AppLovinProcessGradleBuildFile, IPostGenerateGradleAndroidProject
|
||||
{
|
||||
public void OnPostGenerateGradleAndroidProject(string path)
|
||||
{
|
||||
if (!AppLovinSettings.Instance.QualityServiceEnabled) return;
|
||||
|
||||
var failedToAddPlugin = !AddQualityServiceToRootGradleFile(path);
|
||||
if (failedToAddPlugin)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Failed to add AppLovin Quality Service plugin to the gradle project.");
|
||||
return;
|
||||
}
|
||||
|
||||
// The plugin needs to be added to the application module (named launcher)
|
||||
var applicationGradleBuildFilePath = Path.Combine(path, "../launcher/build.gradle");
|
||||
|
||||
if (!File.Exists(applicationGradleBuildFilePath))
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Couldn't find build.gradle file. Failed to add AppLovin Quality Service plugin to the gradle project.");
|
||||
return;
|
||||
}
|
||||
|
||||
AddAppLovinQualityServicePlugin(applicationGradleBuildFilePath);
|
||||
}
|
||||
|
||||
public int callbackOrder
|
||||
{
|
||||
get { return CallbackOrder; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f75e54e2eb78f427ca8643c97684387b
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPostProcessAndroidGradle.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,686 @@
|
||||
//
|
||||
// MaxIntegrationManager.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 8/29/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_IOS || UNITY_IPHONE
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AppLovinMax.Internal;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Callbacks;
|
||||
using UnityEditor.iOS.Xcode.Extensions;
|
||||
using UnityEditor.iOS.Xcode;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public class SkAdNetworkData
|
||||
{
|
||||
[SerializeField] public string[] SkAdNetworkIds;
|
||||
}
|
||||
|
||||
public class AppLovinPostProcessiOS
|
||||
{
|
||||
private const string OutputFileName = "AppLovinQualityServiceSetup.rb";
|
||||
|
||||
// Use a priority of 90 to have AppLovin embed frameworks after Pods are installed (EDM finishes installing Pods at priority 60) and before Firebase Crashlytics runs their scripts (at priority 100).
|
||||
private const int AppLovinEmbedFrameworksPriority = 90;
|
||||
|
||||
private const string TargetUnityIphonePodfileLine = "target 'Unity-iPhone' do";
|
||||
private const string UseFrameworksPodfileLine = "use_frameworks!";
|
||||
private const string UseFrameworksDynamicPodfileLine = "use_frameworks! :linkage => :dynamic";
|
||||
private const string UseFrameworksStaticPodfileLine = "use_frameworks! :linkage => :static";
|
||||
|
||||
private const string ResourcesDirectoryName = "Resources";
|
||||
private const string AppLovinMaxResourcesDirectoryName = "AppLovinMAXResources";
|
||||
private const string AppLovinAdvertisingAttributionEndpoint = "https://postbacks-app.com";
|
||||
|
||||
private const string AppLovinSettingsPlistFileName = "AppLovin-Settings.plist";
|
||||
|
||||
private const string KeySdkKey = "SdkKey";
|
||||
|
||||
private const string AppLovinVerboseLoggingOnKey = "AppLovinVerboseLoggingOn";
|
||||
|
||||
private const string KeyConsentFlowInfo = "ConsentFlowInfo";
|
||||
private const string KeyConsentFlowEnabled = "ConsentFlowEnabled";
|
||||
private const string KeyConsentFlowTermsOfService = "ConsentFlowTermsOfService";
|
||||
private const string KeyConsentFlowPrivacyPolicy = "ConsentFlowPrivacyPolicy";
|
||||
private const string KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR = "ConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR";
|
||||
private const string KeyConsentFlowDebugUserGeography = "ConsentFlowDebugUserGeography";
|
||||
|
||||
private const string KeyAppLovinSdkKeyToRemove = "AppLovinSdkKey";
|
||||
|
||||
private static readonly Regex PodfilePodLineRegex = new Regex("pod \'([^\']*)\'");
|
||||
|
||||
/// <summary>
|
||||
/// Adds AppLovin Quality Service to the iOS project once the project has been exported.
|
||||
///
|
||||
/// 1. Downloads the Quality Service ruby script.
|
||||
/// 2. Runs the script using Ruby which integrates AppLovin Quality Service to the project.
|
||||
/// </summary>
|
||||
[PostProcessBuild(AppLovinPreProcess.CallbackOrder)] // We want to run Quality Service script last.
|
||||
public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
|
||||
{
|
||||
if (!AppLovinSettings.Instance.QualityServiceEnabled) return;
|
||||
|
||||
var sdkKey = AppLovinSettings.Instance.SdkKey;
|
||||
if (string.IsNullOrEmpty(sdkKey))
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. SDK Key is empty. Please enter the AppLovin SDK Key in the Integration Manager.");
|
||||
return;
|
||||
}
|
||||
|
||||
var outputFilePath = Path.Combine(buildPath, OutputFileName);
|
||||
|
||||
// Check if Quality Service is already installed.
|
||||
if (File.Exists(outputFilePath) && Directory.Exists(Path.Combine(buildPath, "AppLovinQualityService")))
|
||||
{
|
||||
// TODO: Check if there is a way to validate if the SDK key matches the script. Else the pub can't use append when/if they change the SDK Key.
|
||||
return;
|
||||
}
|
||||
|
||||
var webRequestConfig = new WebRequestConfig()
|
||||
{
|
||||
DownloadHandler = new DownloadHandlerFile(outputFilePath),
|
||||
JsonString = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey),
|
||||
EndPoint = "https://api2.safedk.com/v1/build/ios_setup2",
|
||||
RequestType = WebRequestType.Post,
|
||||
};
|
||||
|
||||
webRequestConfig.Headers.Add("Content-Type", "application/json");
|
||||
|
||||
var maxWebRequest = new MaxWebRequest(webRequestConfig);
|
||||
|
||||
var webResponse = maxWebRequest.SendSync();
|
||||
if (!webResponse.IsSuccess)
|
||||
{
|
||||
MaxSdkLogger.UserError("AppLovin Quality Service installation failed. Failed to download script with error: " + webResponse.ErrorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Ruby is installed
|
||||
var rubyVersion = AppLovinCommandLine.Run("ruby", "--version", buildPath);
|
||||
if (rubyVersion.ExitCode != 0)
|
||||
{
|
||||
MaxSdkLogger.UserError("AppLovin Quality Service installation requires Ruby. Please install Ruby, export it to your system PATH and re-export the project.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ruby is installed, run `ruby AppLovinQualityServiceSetup.rb`
|
||||
var result = AppLovinCommandLine.Run("ruby", OutputFileName, buildPath);
|
||||
|
||||
// Check if we have an error.
|
||||
if (result.ExitCode != 0) MaxSdkLogger.UserError("Failed to set up AppLovin Quality Service");
|
||||
|
||||
MaxSdkLogger.UserDebug(result.Message);
|
||||
}
|
||||
|
||||
[PostProcessBuild(AppLovinEmbedFrameworksPriority)]
|
||||
public static void MaxPostProcessPbxProject(BuildTarget buildTarget, string buildPath)
|
||||
{
|
||||
var projectPath = PBXProject.GetPBXProjectPath(buildPath);
|
||||
var project = new PBXProject();
|
||||
project.ReadFromFile(projectPath);
|
||||
|
||||
var unityMainTargetGuid = project.GetUnityMainTargetGuid();
|
||||
var unityFrameworkTargetGuid = project.GetUnityFrameworkTargetGuid();
|
||||
|
||||
EmbedDynamicLibrariesIfNeeded(buildPath, project, unityMainTargetGuid);
|
||||
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe, "de", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn, "en", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs, "es", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr, "fr", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa, "ja", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo, "ko", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans, "zh-Hans", buildPath, project, unityMainTargetGuid);
|
||||
LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant, "zh-Hant", buildPath, project, unityMainTargetGuid);
|
||||
|
||||
AddSwiftSupport(buildPath, project, unityFrameworkTargetGuid, unityMainTargetGuid);
|
||||
AddYandexSettingsIfNeeded(project, unityMainTargetGuid);
|
||||
|
||||
project.WriteToFile(projectPath);
|
||||
}
|
||||
|
||||
private static void EmbedDynamicLibrariesIfNeeded(string buildPath, PBXProject project, string targetGuid)
|
||||
{
|
||||
// Check that the Pods directory exists (it might not if a publisher is building with Generate Podfile setting disabled in EDM).
|
||||
var podsDirectory = Path.Combine(buildPath, "Pods");
|
||||
if (!Directory.Exists(podsDirectory) || !ShouldEmbedDynamicLibraries(buildPath)) return;
|
||||
|
||||
var dynamicLibraryPathsToEmbed = GetDynamicLibraryPathsToEmbed(podsDirectory, buildPath);
|
||||
if (dynamicLibraryPathsToEmbed == null || dynamicLibraryPathsToEmbed.Count == 0) return;
|
||||
|
||||
foreach (var dynamicLibraryPath in dynamicLibraryPathsToEmbed)
|
||||
{
|
||||
var fileGuid = project.AddFile(dynamicLibraryPath, dynamicLibraryPath);
|
||||
project.AddFileToEmbedFrameworks(targetGuid, fileGuid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// |-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
/// | embed | use_frameworks! (:linkage => :dynamic) | use_frameworks! :linkage => :static | `use_frameworks!` line not present |
|
||||
/// |---------------------------|------------------------------------------|---------------------------------------|--------------------------------------|
|
||||
/// | Unity-iPhone present | Do not embed dynamic libraries | Embed dynamic libraries | Do not embed dynamic libraries |
|
||||
/// | Unity-iPhone not present | Embed dynamic libraries | Embed dynamic libraries | Embed dynamic libraries |
|
||||
/// |-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
/// </summary>
|
||||
/// <param name="buildPath">An iOS build path</param>
|
||||
/// <returns>Whether or not the dynamic libraries should be embedded.</returns>
|
||||
private static bool ShouldEmbedDynamicLibraries(string buildPath)
|
||||
{
|
||||
var podfilePath = Path.Combine(buildPath, "Podfile");
|
||||
if (!File.Exists(podfilePath)) return false;
|
||||
|
||||
// If the Podfile doesn't have a `Unity-iPhone` target, we should embed the dynamic libraries.
|
||||
var lines = File.ReadAllLines(podfilePath);
|
||||
var containsUnityIphoneTarget = lines.Any(line => line.Contains(TargetUnityIphonePodfileLine));
|
||||
if (!containsUnityIphoneTarget) return true;
|
||||
|
||||
// If the Podfile does not have a `use_frameworks! :linkage => static` line, we should not embed the dynamic libraries.
|
||||
var useFrameworksStaticLineIndex = Array.FindIndex(lines, line => line.Contains(UseFrameworksStaticPodfileLine));
|
||||
if (useFrameworksStaticLineIndex == -1) return false;
|
||||
|
||||
// If more than one of the `use_frameworks!` lines are present, CocoaPods will use the last one.
|
||||
var useFrameworksLineIndex = Array.FindIndex(lines, line => line.Trim() == UseFrameworksPodfileLine); // Check for exact line to avoid matching `use_frameworks! :linkage => static/dynamic`
|
||||
var useFrameworksDynamicLineIndex = Array.FindIndex(lines, line => line.Contains(UseFrameworksDynamicPodfileLine));
|
||||
|
||||
// Check if `use_frameworks! :linkage => :static` is the last line of the three. If it is, we should embed the dynamic libraries.
|
||||
return useFrameworksLineIndex < useFrameworksStaticLineIndex && useFrameworksDynamicLineIndex < useFrameworksStaticLineIndex;
|
||||
}
|
||||
|
||||
private static List<string> GetDynamicLibraryPathsToEmbed(string podsDirectory, string buildPath)
|
||||
{
|
||||
var podfilePath = Path.Combine(buildPath, "Podfile");
|
||||
var dynamicLibraryFrameworksToEmbed = GetDynamicLibraryFrameworksToEmbed(podfilePath);
|
||||
|
||||
return GetDynamicLibraryPathsInProjectToEmbed(podsDirectory, dynamicLibraryFrameworksToEmbed);
|
||||
}
|
||||
|
||||
private static List<string> GetDynamicLibraryFrameworksToEmbed(string podfilePath)
|
||||
{
|
||||
var dynamicLibrariesToEmbed = GetDynamicLibrariesToEmbed();
|
||||
|
||||
var podsInUnityIphoneTarget = GetPodNamesInUnityIphoneTarget(podfilePath);
|
||||
var dynamicLibrariesToIgnore = dynamicLibrariesToEmbed.Where(dynamicLibraryToEmbed => podsInUnityIphoneTarget.Contains(dynamicLibraryToEmbed.PodName)).ToList();
|
||||
|
||||
// Determine frameworks to embed based on the dynamic libraries to embed and ignore
|
||||
var dynamicLibraryFrameworksToIgnore = dynamicLibrariesToIgnore.SelectMany(library => library.FrameworkNames).Distinct().ToList();
|
||||
return dynamicLibrariesToEmbed.SelectMany(library => library.FrameworkNames).Except(dynamicLibraryFrameworksToIgnore).Distinct().ToList();
|
||||
}
|
||||
|
||||
private static List<DynamicLibraryToEmbed> GetDynamicLibrariesToEmbed()
|
||||
{
|
||||
var pluginData = AppLovinIntegrationManager.LoadPluginDataSync();
|
||||
if (pluginData == null)
|
||||
{
|
||||
MaxSdkLogger.E("Failed to load plugin data. Dynamic libraries will not be embedded.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the dynamic libraries to embed for each network
|
||||
var librariesToAdd = pluginData.MediatedNetworks
|
||||
.Where(network => network.DynamicLibrariesToEmbed != null)
|
||||
.SelectMany(network => network.DynamicLibrariesToEmbed
|
||||
.Where(libraryToEmbed => IsRequiredNetworkVersionInstalled(libraryToEmbed, network)))
|
||||
.ToList();
|
||||
|
||||
// Get the dynamic libraries to embed for AppLovin MAX
|
||||
if (pluginData.AppLovinMax.DynamicLibrariesToEmbed != null)
|
||||
{
|
||||
librariesToAdd.AddRange(pluginData.AppLovinMax.DynamicLibrariesToEmbed);
|
||||
}
|
||||
|
||||
// Get the dynamic libraries to embed for third parties
|
||||
if (pluginData.ThirdPartyDynamicLibrariesToEmbed != null)
|
||||
{
|
||||
// TODO: Add version check for third party dynamic libraries.
|
||||
librariesToAdd.AddRange(pluginData.ThirdPartyDynamicLibrariesToEmbed);
|
||||
}
|
||||
|
||||
return librariesToAdd;
|
||||
}
|
||||
|
||||
private static List<string> GetPodNamesInUnityIphoneTarget(string podfilePath)
|
||||
{
|
||||
var lines = File.ReadAllLines(podfilePath);
|
||||
var podNamesInUnityIphone = new List<string>();
|
||||
|
||||
var insideUnityIphoneTarget = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
// Loop until we find the `target 'Unity-iPhone'` line
|
||||
if (insideUnityIphoneTarget)
|
||||
{
|
||||
if (line.Trim() == "end") break;
|
||||
|
||||
if (PodfilePodLineRegex.IsMatch(line))
|
||||
{
|
||||
var podName = PodfilePodLineRegex.Match(line).Groups[1].Value;
|
||||
podNamesInUnityIphone.Add(podName);
|
||||
}
|
||||
}
|
||||
else if (line.Contains(TargetUnityIphonePodfileLine))
|
||||
{
|
||||
insideUnityIphoneTarget = true;
|
||||
}
|
||||
}
|
||||
|
||||
return podNamesInUnityIphone;
|
||||
}
|
||||
|
||||
private static bool IsRequiredNetworkVersionInstalled(DynamicLibraryToEmbed libraryToEmbed, Network network)
|
||||
{
|
||||
var currentIosVersion = network.CurrentVersions.Ios;
|
||||
if (string.IsNullOrEmpty(currentIosVersion)) return false;
|
||||
|
||||
return MaxSdkUtils.IsVersionInRange(currentIosVersion, libraryToEmbed.MinVersion, libraryToEmbed.MaxVersion);
|
||||
}
|
||||
|
||||
private static List<string> GetDynamicLibraryPathsInProjectToEmbed(string podsDirectory, List<string> dynamicLibrariesToEmbed)
|
||||
{
|
||||
var dynamicLibraryPathsPresentInProject = new List<string>();
|
||||
foreach (var dynamicLibraryToSearch in dynamicLibrariesToEmbed)
|
||||
{
|
||||
// both .framework and .xcframework are directories, not files
|
||||
var directories = Directory.GetDirectories(podsDirectory, dynamicLibraryToSearch, SearchOption.AllDirectories);
|
||||
if (directories.Length <= 0) continue;
|
||||
|
||||
var dynamicLibraryAbsolutePath = directories[0];
|
||||
var relativePath = GetDynamicLibraryRelativePath(dynamicLibraryAbsolutePath);
|
||||
dynamicLibraryPathsPresentInProject.Add(relativePath);
|
||||
}
|
||||
|
||||
return dynamicLibraryPathsPresentInProject;
|
||||
}
|
||||
|
||||
private static string GetDynamicLibraryRelativePath(string dynamicLibraryAbsolutePath)
|
||||
{
|
||||
var index = dynamicLibraryAbsolutePath.LastIndexOf("Pods", StringComparison.Ordinal);
|
||||
return dynamicLibraryAbsolutePath.Substring(index);
|
||||
}
|
||||
|
||||
private static void LocalizeUserTrackingDescriptionIfNeeded(string localizedUserTrackingDescription, string localeCode, string buildPath, PBXProject project, string targetGuid)
|
||||
{
|
||||
var resourcesDirectoryPath = Path.Combine(buildPath, AppLovinMaxResourcesDirectoryName);
|
||||
var localeSpecificDirectoryName = localeCode + ".lproj";
|
||||
var localeSpecificDirectoryPath = Path.Combine(resourcesDirectoryPath, localeSpecificDirectoryName);
|
||||
var infoPlistStringsFilePath = Path.Combine(localeSpecificDirectoryPath, "InfoPlist.strings");
|
||||
|
||||
// Check if localization has been disabled between builds, and remove them as needed.
|
||||
if (ShouldRemoveLocalization(localizedUserTrackingDescription))
|
||||
{
|
||||
if (!File.Exists(infoPlistStringsFilePath)) return;
|
||||
|
||||
File.Delete(infoPlistStringsFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
// Log an error if we detect a localization file for this language in the `Resources` directory
|
||||
var legacyResourcedDirectoryPath = Path.Combine(buildPath, ResourcesDirectoryName);
|
||||
var localeSpecificLegacyDirectoryPath = Path.Combine(legacyResourcedDirectoryPath, localeSpecificDirectoryName);
|
||||
if (Directory.Exists(localeSpecificLegacyDirectoryPath))
|
||||
{
|
||||
MaxSdkLogger.UserError("Detected existing localization resource for \"" + localeCode + "\" locale. Skipping localization for User Tracking Usage Description. Please disable localization in AppLovin Integration manager and add the localizations to your existing resource.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create intermediate directories as needed.
|
||||
if (!Directory.Exists(resourcesDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(resourcesDirectoryPath);
|
||||
}
|
||||
|
||||
if (!Directory.Exists(localeSpecificDirectoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(localeSpecificDirectoryPath);
|
||||
}
|
||||
|
||||
var localizedDescriptionLine = "\"NSUserTrackingUsageDescription\" = \"" + localizedUserTrackingDescription + "\";\n";
|
||||
// File already exists, update it in case the value changed between builds.
|
||||
if (File.Exists(infoPlistStringsFilePath))
|
||||
{
|
||||
var output = new List<string>();
|
||||
var lines = File.ReadAllLines(infoPlistStringsFilePath);
|
||||
var keyUpdated = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains("NSUserTrackingUsageDescription"))
|
||||
{
|
||||
output.Add(localizedDescriptionLine);
|
||||
keyUpdated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
output.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (!keyUpdated)
|
||||
{
|
||||
output.Add(localizedDescriptionLine);
|
||||
}
|
||||
|
||||
File.WriteAllText(infoPlistStringsFilePath, string.Join("\n", output.ToArray()) + "\n");
|
||||
}
|
||||
// File doesn't exist, create one.
|
||||
else
|
||||
{
|
||||
File.WriteAllText(infoPlistStringsFilePath, "/* Localized versions of Info.plist keys - Generated by AL MAX plugin */\n" + localizedDescriptionLine);
|
||||
}
|
||||
|
||||
var localeSpecificDirectoryRelativePath = Path.Combine(AppLovinMaxResourcesDirectoryName, localeSpecificDirectoryName);
|
||||
var guid = project.AddFolderReference(localeSpecificDirectoryRelativePath, localeSpecificDirectoryRelativePath);
|
||||
project.AddFileToBuild(targetGuid, guid);
|
||||
}
|
||||
|
||||
private static bool ShouldRemoveLocalization(string localizedUserTrackingDescription)
|
||||
{
|
||||
if (string.IsNullOrEmpty(localizedUserTrackingDescription)) return true;
|
||||
|
||||
var internalSettings = AppLovinInternalSettings.Instance;
|
||||
return !internalSettings.ConsentFlowEnabled || !internalSettings.UserTrackingUsageLocalizationEnabled;
|
||||
}
|
||||
|
||||
private static void AddSwiftSupport(string buildPath, PBXProject project, string unityFrameworkTargetGuid, string unityMainTargetGuid)
|
||||
{
|
||||
var swiftFileRelativePath = "Classes/MAXSwiftSupport.swift";
|
||||
var swiftFilePath = Path.Combine(buildPath, swiftFileRelativePath);
|
||||
|
||||
// Add Swift file
|
||||
CreateSwiftFile(swiftFilePath);
|
||||
var swiftFileGuid = project.AddFile(swiftFileRelativePath, swiftFileRelativePath);
|
||||
project.AddFileToBuild(unityFrameworkTargetGuid, swiftFileGuid);
|
||||
|
||||
// Add Swift version property if needed
|
||||
var swiftVersion = project.GetBuildPropertyForAnyConfig(unityFrameworkTargetGuid, "SWIFT_VERSION");
|
||||
if (string.IsNullOrEmpty(swiftVersion))
|
||||
{
|
||||
project.SetBuildProperty(unityFrameworkTargetGuid, "SWIFT_VERSION", "5.0");
|
||||
}
|
||||
|
||||
// Some publishers may configure these settings in their own post-processing scripts.
|
||||
// Only set them if they haven't already been defined to avoid overwriting publisher-defined values.
|
||||
var enableModules = project.GetBuildPropertyForAnyConfig(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES");
|
||||
if (string.IsNullOrEmpty(enableModules))
|
||||
{
|
||||
project.SetBuildProperty(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES", "YES");
|
||||
}
|
||||
|
||||
var alwaysEmbedSwiftLibraries = project.GetBuildPropertyForAnyConfig(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES");
|
||||
if (string.IsNullOrEmpty(alwaysEmbedSwiftLibraries))
|
||||
{
|
||||
project.SetBuildProperty(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateSwiftFile(string swiftFilePath)
|
||||
{
|
||||
if (File.Exists(swiftFilePath)) return;
|
||||
|
||||
// Create a file to write to.
|
||||
using (var writer = File.CreateText(swiftFilePath))
|
||||
{
|
||||
writer.WriteLine("//\n// MAXSwiftSupport.swift\n//");
|
||||
writer.WriteLine("\nimport Foundation\n");
|
||||
writer.WriteLine("// This file ensures the project includes Swift support.");
|
||||
writer.WriteLine("// It is automatically generated by the MAX Unity Plugin.");
|
||||
writer.Close();
|
||||
}
|
||||
}
|
||||
|
||||
[PostProcessBuild(AppLovinPreProcess.CallbackOrder)]
|
||||
public static void MaxPostProcessPlist(BuildTarget buildTarget, string path)
|
||||
{
|
||||
var plistPath = Path.Combine(path, "Info.plist");
|
||||
var plist = new PlistDocument();
|
||||
plist.ReadFromFile(plistPath);
|
||||
|
||||
RemoveAttributionReportEndpointIfNeeded(plist);
|
||||
|
||||
EnableVerboseLoggingIfNeeded(plist);
|
||||
AddGoogleApplicationIdIfNeeded(plist);
|
||||
|
||||
AddSdkSettings(plist, path);
|
||||
AddSkAdNetworksInfoIfNeeded(plist);
|
||||
RemoveSdkKeyIfNeeded(plist);
|
||||
|
||||
plist.WriteToFile(plistPath);
|
||||
}
|
||||
|
||||
private static void RemoveAttributionReportEndpointIfNeeded(PlistDocument plist)
|
||||
{
|
||||
PlistElement attributionReportEndPoint;
|
||||
plist.root.values.TryGetValue("NSAdvertisingAttributionReportEndpoint", out attributionReportEndPoint);
|
||||
|
||||
// We no longer support this feature. Check if we had previously set the attribution endpoint and un-set it.
|
||||
if (attributionReportEndPoint == null || !AppLovinAdvertisingAttributionEndpoint.Equals(attributionReportEndPoint.AsString())) return;
|
||||
|
||||
MaxSdkLogger.UserWarning("Global SKAdNetwork postback forwarding is no longer supported by AppLovin. Removing AppLovin Advertising Attribution Endpoint from Info.plist.");
|
||||
plist.root.values.Remove("NSAdvertisingAttributionReportEndpoint");
|
||||
}
|
||||
|
||||
private static void EnableVerboseLoggingIfNeeded(PlistDocument plist)
|
||||
{
|
||||
if (!EditorPrefs.HasKey(MaxSdkLogger.KeyVerboseLoggingEnabled)) return;
|
||||
|
||||
var enabled = EditorPrefs.GetBool(MaxSdkLogger.KeyVerboseLoggingEnabled);
|
||||
if (enabled)
|
||||
{
|
||||
plist.root.SetBoolean(AppLovinVerboseLoggingOnKey, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
plist.root.values.Remove(AppLovinVerboseLoggingOnKey);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddGoogleApplicationIdIfNeeded(PlistDocument plist)
|
||||
{
|
||||
if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
|
||||
|
||||
const string googleApplicationIdentifier = "GADApplicationIdentifier";
|
||||
var appId = AppLovinSettings.Instance.AdMobIosAppId;
|
||||
// Log error if the App ID is not set.
|
||||
if (string.IsNullOrEmpty(appId) || !appId.StartsWith("ca-app-pub-"))
|
||||
{
|
||||
MaxSdkLogger.UserError("[AppLovin MAX] Google App ID is not set. Please enter a valid app ID within the AppLovin Integration Manager window.");
|
||||
return;
|
||||
}
|
||||
|
||||
plist.root.SetString(googleApplicationIdentifier, appId);
|
||||
}
|
||||
|
||||
private static void AddYandexSettingsIfNeeded(PBXProject project, string unityMainTargetGuid)
|
||||
{
|
||||
if (!AppLovinPackageManager.IsAdapterInstalled("Yandex")) return;
|
||||
|
||||
if (MaxSdkUtils.CompareVersions(PlayerSettings.iOS.targetOSVersionString, "12.0") == MaxSdkUtils.VersionComparisonResult.Lesser)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Your iOS target version is under the minimum required version by Yandex. Please update it to 12.0 or newer in your ProjectSettings and rebuild your project.");
|
||||
return;
|
||||
}
|
||||
|
||||
project.SetBuildProperty(unityMainTargetGuid, "GENERATE_INFOPLIST_FILE", "NO");
|
||||
}
|
||||
|
||||
private static void AddSdkSettings(PlistDocument infoPlist, string buildPath)
|
||||
{
|
||||
var sdkSettingsPlistPath = Path.Combine(buildPath, AppLovinSettingsPlistFileName);
|
||||
var sdkSettingsPlist = new PlistDocument();
|
||||
if (File.Exists(sdkSettingsPlistPath))
|
||||
{
|
||||
sdkSettingsPlist.ReadFromFile(sdkSettingsPlistPath);
|
||||
}
|
||||
|
||||
// Add the SDK key to the SDK settings plist.
|
||||
sdkSettingsPlist.root.SetString(KeySdkKey, AppLovinSettings.Instance.SdkKey);
|
||||
|
||||
// Add consent flow settings if needed.
|
||||
EnableConsentFlowIfNeeded(sdkSettingsPlist, infoPlist);
|
||||
|
||||
sdkSettingsPlist.WriteToFile(sdkSettingsPlistPath);
|
||||
|
||||
var projectPath = PBXProject.GetPBXProjectPath(buildPath);
|
||||
var project = new PBXProject();
|
||||
project.ReadFromFile(projectPath);
|
||||
|
||||
var unityMainTargetGuid = project.GetUnityMainTargetGuid();
|
||||
|
||||
var guid = project.AddFile(AppLovinSettingsPlistFileName, AppLovinSettingsPlistFileName);
|
||||
project.AddFileToBuild(unityMainTargetGuid, guid);
|
||||
project.WriteToFile(projectPath);
|
||||
}
|
||||
|
||||
private static void EnableConsentFlowIfNeeded(PlistDocument applovinSettingsPlist, PlistDocument infoPlist)
|
||||
{
|
||||
var consentFlowEnabled = AppLovinInternalSettings.Instance.ConsentFlowEnabled;
|
||||
if (!consentFlowEnabled) return;
|
||||
|
||||
var userTrackingUsageDescription = AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn;
|
||||
var privacyPolicyUrl = AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl;
|
||||
if (string.IsNullOrEmpty(userTrackingUsageDescription) || string.IsNullOrEmpty(privacyPolicyUrl))
|
||||
{
|
||||
AppLovinIntegrationManager.ShowBuildFailureDialog("You cannot use the AppLovin SDK's consent flow without defining a Privacy Policy URL and the `User Tracking Usage Description` in the AppLovin Integration Manager. \n\n" +
|
||||
"Both values must be included to enable the SDK's consent flow.");
|
||||
|
||||
// No need to update the info.plist here. Default consent flow state will be determined on the SDK side.
|
||||
return;
|
||||
}
|
||||
|
||||
var consentFlowInfoRoot = applovinSettingsPlist.root.CreateDict(KeyConsentFlowInfo);
|
||||
consentFlowInfoRoot.SetBoolean(KeyConsentFlowEnabled, consentFlowEnabled);
|
||||
consentFlowInfoRoot.SetString(KeyConsentFlowPrivacyPolicy, privacyPolicyUrl);
|
||||
|
||||
var termsOfServiceUrl = AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl;
|
||||
if (MaxSdkUtils.IsValidString(termsOfServiceUrl))
|
||||
{
|
||||
consentFlowInfoRoot.SetString(KeyConsentFlowTermsOfService, termsOfServiceUrl);
|
||||
}
|
||||
|
||||
var shouldShowTermsAndPrivacyPolicyAlertInGdpr = AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR;
|
||||
consentFlowInfoRoot.SetBoolean(KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR, shouldShowTermsAndPrivacyPolicyAlertInGdpr);
|
||||
|
||||
var debugUserGeography = AppLovinInternalSettings.Instance.DebugUserGeography;
|
||||
if (debugUserGeography == MaxSdkBase.ConsentFlowUserGeography.Gdpr)
|
||||
{
|
||||
consentFlowInfoRoot.SetString(KeyConsentFlowDebugUserGeography, "gdpr");
|
||||
}
|
||||
|
||||
infoPlist.root.SetString("NSUserTrackingUsageDescription", userTrackingUsageDescription);
|
||||
}
|
||||
|
||||
private static void AddSkAdNetworksInfoIfNeeded(PlistDocument plist)
|
||||
{
|
||||
var skAdNetworkData = GetSkAdNetworkData();
|
||||
var skAdNetworkIds = skAdNetworkData.SkAdNetworkIds;
|
||||
// Check if we have a valid list of SKAdNetworkIds that need to be added.
|
||||
if (skAdNetworkIds == null || skAdNetworkIds.Length < 1) return;
|
||||
|
||||
//
|
||||
// Add the SKAdNetworkItems to the plist. It should look like following:
|
||||
//
|
||||
// <key>SKAdNetworkItems</key>
|
||||
// <array>
|
||||
// <dict>
|
||||
// <key>SKAdNetworkIdentifier</key>
|
||||
// <string>ABC123XYZ.skadnetwork</string>
|
||||
// </dict>
|
||||
// <dict>
|
||||
// <key>SKAdNetworkIdentifier</key>
|
||||
// <string>123QWE456.skadnetwork</string>
|
||||
// </dict>
|
||||
// <dict>
|
||||
// <key>SKAdNetworkIdentifier</key>
|
||||
// <string>987XYZ123.skadnetwork</string>
|
||||
// </dict>
|
||||
// </array>
|
||||
//
|
||||
PlistElement skAdNetworkItems;
|
||||
plist.root.values.TryGetValue("SKAdNetworkItems", out skAdNetworkItems);
|
||||
var existingSkAdNetworkIds = new HashSet<string>();
|
||||
// Check if SKAdNetworkItems array is already in the Plist document and collect all the IDs that are already present.
|
||||
if (skAdNetworkItems != null && skAdNetworkItems.GetType() == typeof(PlistElementArray))
|
||||
{
|
||||
var plistElementDictionaries = skAdNetworkItems.AsArray().values.Where(plistElement => plistElement.GetType() == typeof(PlistElementDict));
|
||||
foreach (var plistElement in plistElementDictionaries)
|
||||
{
|
||||
PlistElement existingId;
|
||||
plistElement.AsDict().values.TryGetValue("SKAdNetworkIdentifier", out existingId);
|
||||
if (existingId == null || existingId.GetType() != typeof(PlistElementString) || string.IsNullOrEmpty(existingId.AsString())) continue;
|
||||
|
||||
existingSkAdNetworkIds.Add(existingId.AsString());
|
||||
}
|
||||
}
|
||||
// Else, create an array of SKAdNetworkItems into which we will add our IDs.
|
||||
else
|
||||
{
|
||||
skAdNetworkItems = plist.root.CreateArray("SKAdNetworkItems");
|
||||
}
|
||||
|
||||
foreach (var skAdNetworkId in skAdNetworkIds)
|
||||
{
|
||||
// Skip adding IDs that are already in the array.
|
||||
if (existingSkAdNetworkIds.Contains(skAdNetworkId)) continue;
|
||||
|
||||
var skAdNetworkItemDict = skAdNetworkItems.AsArray().AddDict();
|
||||
skAdNetworkItemDict.SetString("SKAdNetworkIdentifier", skAdNetworkId);
|
||||
}
|
||||
}
|
||||
|
||||
private static SkAdNetworkData GetSkAdNetworkData()
|
||||
{
|
||||
// Get the list of installed ad networks to be passed up
|
||||
var installedNetworks = AppLovinPackageManager.GetInstalledMediationNetworks();
|
||||
var uriBuilder = new UriBuilder("https://unity.applovin.com/max/1.0/skadnetwork_ids");
|
||||
var adNetworks = string.Join(",", installedNetworks.ToArray());
|
||||
if (MaxSdkUtils.IsValidString(adNetworks))
|
||||
{
|
||||
uriBuilder.Query += string.Format("ad_networks={0}", adNetworks);
|
||||
}
|
||||
|
||||
var webRequestConfig = new WebRequestConfig()
|
||||
{
|
||||
EndPoint = uriBuilder.ToString()
|
||||
};
|
||||
|
||||
var maxWebRequest = new MaxWebRequest(webRequestConfig);
|
||||
var webResponse = maxWebRequest.SendSync();
|
||||
|
||||
if (!webResponse.IsSuccess)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to retrieve SKAdNetwork IDs with error: " + webResponse.ErrorMessage);
|
||||
return new SkAdNetworkData();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonUtility.FromJson<SkAdNetworkData>(webResponse.ResponseMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to parse data '" + webResponse.ResponseMessage + "' with exception: " + exception);
|
||||
return new SkAdNetworkData();
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveSdkKeyIfNeeded(PlistDocument plist)
|
||||
{
|
||||
if (!plist.root.values.ContainsKey(KeyAppLovinSdkKeyToRemove)) return;
|
||||
|
||||
plist.root.values.Remove(KeyAppLovinSdkKeyToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5d209f90444f4a90830b48b5f3f3ff4
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPostProcessiOS.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,182 @@
|
||||
//
|
||||
// AppLovinPreProcess.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Jonathan Liu on 10/19/2023.
|
||||
// Copyright © 2023 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
public abstract class AppLovinPreProcess
|
||||
{
|
||||
// Use a slightly lower value than max value so pubs have the option to run a post process script after ours.
|
||||
internal const int CallbackOrder = int.MaxValue - 10;
|
||||
private const string AppLovinDependenciesFileExportPath = "MaxSdk/AppLovin/Editor/Dependencies.xml";
|
||||
private const string ElementNameDependencies = "dependencies";
|
||||
|
||||
private static readonly XmlWriterSettings DependenciesFileXmlWriterSettings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
IndentChars = " ",
|
||||
NewLineChars = "\n",
|
||||
NewLineHandling = NewLineHandling.Replace
|
||||
};
|
||||
|
||||
protected static string AppLovinDependenciesFilePath
|
||||
{
|
||||
get { return AppLovinIntegrationManager.IsPluginInPackageManager ? Path.Combine("Assets", AppLovinDependenciesFileExportPath) : MaxSdkUtils.GetAssetPathForExportPath(AppLovinDependenciesFileExportPath); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the AppLovin Dependencies.xml file. If `createIfNotExists` is true, a new file will be created if one does not exist.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the AppLovin Dependencies.xml file</param>
|
||||
/// <param name="createIfNotExists">Whether to create a new Dependencies.xml file if one does not exist</param>
|
||||
/// <returns></returns>
|
||||
protected static XDocument GetAppLovinDependenciesFile(string path, bool createIfNotExists = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
return XDocument.Load(path);
|
||||
}
|
||||
|
||||
if (createIfNotExists)
|
||||
{
|
||||
return new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
|
||||
new XElement(ElementNameDependencies));
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.E("Unable to load Dependencies file due to exception: " + exception.Message);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a dependency if it exists, otherwise adds a new dependency.
|
||||
/// </summary>
|
||||
/// <param name="dependenciesDocument">The dependencies document we are writing to</param>
|
||||
/// <param name="parentTag">The parent tag that we want to search for the dependency. For example, to add a new dependency to Android, pass in "androidPackages"</param>
|
||||
/// <param name="elementTag">The element we are looking to update/add. For example, to add a new dependency to Android, pass in "androidPackage"</param>
|
||||
/// <param name="matchAttribute">The attribute name we want in the dependency. For example, to add something to the spec attribute, pass in "spec" </param>
|
||||
/// <param name="matchValuePrefix">The attribute value prefix we are looking to replace. For example, "com.google.android.ump:user-messaging-platform"</param>
|
||||
/// <param name="newDependency">The new dependency we want to add.</param>
|
||||
protected static void AddOrUpdateDependency(
|
||||
XDocument dependenciesDocument,
|
||||
string parentTag,
|
||||
string elementTag,
|
||||
string matchAttribute,
|
||||
string matchValuePrefix,
|
||||
XElement newDependency)
|
||||
{
|
||||
var parentElement = dependenciesDocument.Root.Element(parentTag);
|
||||
if (parentElement == null)
|
||||
{
|
||||
parentElement = new XElement(parentTag);
|
||||
dependenciesDocument.Root.Add(parentElement);
|
||||
}
|
||||
|
||||
// Check if a dependency exists that matches the attributes name and value
|
||||
var existingElement = parentElement.Elements(elementTag)
|
||||
.FirstOrDefault(element =>
|
||||
{
|
||||
var attr = element.Attribute(matchAttribute);
|
||||
return attr != null && attr.Value.StartsWith(matchValuePrefix, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
if (existingElement != null)
|
||||
{
|
||||
foreach (var attr in newDependency.Attributes())
|
||||
{
|
||||
existingElement.SetAttributeValue(attr.Name, attr.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parentElement.Add(newDependency);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a dependency from an xml file.
|
||||
/// </summary>
|
||||
/// <param name="doc">The xml file to remove a dependency from</param>
|
||||
/// <param name="parentTag">The parent tag that we want to search for the dependency to remove. For example: "androidPackages"</param>
|
||||
/// <param name="elementTag">The element we are looking to remove. For example: "androidPackage"</param>
|
||||
/// <param name="matchAttribute">The attribute name we want to remove. For example: "spec" </param>
|
||||
/// <param name="matchValuePrefix">The attribute value prefix we are looking to replace. For example: "com.google.android.ump:user-messaging-platform"</param>
|
||||
/// <returns>True if the dependency was removed successfully, otherwise return false.</returns>
|
||||
protected static bool RemoveDependency(
|
||||
XDocument doc,
|
||||
string parentTag,
|
||||
string elementTag,
|
||||
string matchAttribute,
|
||||
string matchValuePrefix)
|
||||
{
|
||||
var root = doc.Root;
|
||||
if (root == null) return false;
|
||||
|
||||
var parentElement = root.Element(parentTag);
|
||||
if (parentElement == null) return false;
|
||||
|
||||
XElement toRemove = null;
|
||||
foreach (var e in parentElement.Elements(elementTag))
|
||||
{
|
||||
var attr = e.Attribute(matchAttribute);
|
||||
if (attr != null && attr.Value.StartsWith(matchValuePrefix))
|
||||
{
|
||||
toRemove = e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove == null) return false;
|
||||
|
||||
toRemove.Remove();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves an xml file.
|
||||
/// </summary>
|
||||
/// <param name="doc">The document to save</param>
|
||||
/// <param name="path">The path to the document to save</param>
|
||||
/// <returns>Returns true if the file was saved successfully</returns>
|
||||
protected static bool SaveDependenciesFile(XDocument doc, string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Ensure directory exists before saving the file
|
||||
var directory = Path.GetDirectoryName(path);
|
||||
if (MaxSdkUtils.IsValidString(directory))
|
||||
{
|
||||
// Does nothing if directory already exists
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
using (var xmlWriter = XmlWriter.Create(path, DependenciesFileXmlWriterSettings))
|
||||
{
|
||||
doc.Save(xmlWriter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.E("Unable to save Dependencies file due to exception: " + exception.Message);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e6254f24e89548b3a7644fa7bf25f9d
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPreProcess.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// AppLovinBuildPreProcessor.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 8/27/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_ANDROID
|
||||
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the AppLovin Quality Service plugin to the gradle template file. See <see cref="AppLovinProcessGradleBuildFile"/> for more details.
|
||||
/// </summary>
|
||||
public class AppLovinPreProcessAndroid : AppLovinProcessGradleBuildFile, IPreprocessBuildWithReport
|
||||
{
|
||||
private const string ElementNameAndroidPackages = "androidPackages";
|
||||
private const string ElementNameAndroidPackage = "androidPackage";
|
||||
private const string AttributeNameSpec = "spec";
|
||||
private const string UmpDependencyPackage = "com.google.android.ump:user-messaging-platform:";
|
||||
private const string UmpDependencyVersion = "4.0.0";
|
||||
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
PreprocessAppLovinQualityServicePlugin();
|
||||
AddGoogleCmpDependencyIfNeeded();
|
||||
}
|
||||
|
||||
private static void PreprocessAppLovinQualityServicePlugin()
|
||||
{
|
||||
// We can only process gradle template file here. If it is not available, we will try again in post build on Unity IDEs newer than 2018_2 (see AppLovinPostProcessGradleProject).
|
||||
if (!AppLovinIntegrationManager.GradleTemplateEnabled) return;
|
||||
|
||||
// The publisher could be migrating from older Unity versions to 2019_3 or newer.
|
||||
// If so, we should delete the plugin from the template. The plugin will be added to the project's application module in the post processing script (AppLovinPostProcessGradleProject).
|
||||
RemoveAppLovinQualityServiceOrSafeDkPlugin(AppLovinIntegrationManager.GradleTemplatePath);
|
||||
}
|
||||
|
||||
private static void AddGoogleCmpDependencyIfNeeded()
|
||||
{
|
||||
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled)
|
||||
{
|
||||
var umpPackage = new XElement(ElementNameAndroidPackage,
|
||||
new XAttribute(AttributeNameSpec, UmpDependencyPackage + UmpDependencyVersion));
|
||||
var success = AddOrUpdateAndroidDependency(UmpDependencyPackage, umpPackage );
|
||||
if (!success)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Google CMP will not function. Unable to add user-messaging-platform dependency.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveAndroidDependency(UmpDependencyPackage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates an Android dependency in the AppLovin Dependencies.xml file.
|
||||
/// </summary>
|
||||
/// <param name="package">The package that we are trying to update</param>
|
||||
/// <param name="newDependency">The new dependency to add if it doesn't exist</param>
|
||||
/// <returns>Returns true if the file was successfully edited</returns>
|
||||
private static bool AddOrUpdateAndroidDependency(string package, XElement newDependency)
|
||||
{
|
||||
var dependenciesFilePath = AppLovinDependenciesFilePath;
|
||||
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath, AppLovinIntegrationManager.IsPluginInPackageManager);
|
||||
if (dependenciesDocument == null) return false;
|
||||
|
||||
AddOrUpdateDependency(dependenciesDocument,
|
||||
ElementNameAndroidPackages,
|
||||
ElementNameAndroidPackage,
|
||||
AttributeNameSpec,
|
||||
package,
|
||||
newDependency);
|
||||
return SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removed an android dependency from the AppLovin Dependencies.xml file.
|
||||
/// </summary>
|
||||
/// <param name="package">The package to remove</param>
|
||||
private static void RemoveAndroidDependency(string package)
|
||||
{
|
||||
var dependenciesFilePath = AppLovinDependenciesFilePath;
|
||||
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath);
|
||||
if (dependenciesDocument == null) return;
|
||||
|
||||
var removed = RemoveDependency(dependenciesDocument,
|
||||
ElementNameAndroidPackages,
|
||||
ElementNameAndroidPackage,
|
||||
AttributeNameSpec,
|
||||
package);
|
||||
|
||||
if (!removed) return;
|
||||
|
||||
SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
|
||||
}
|
||||
|
||||
public int callbackOrder
|
||||
{
|
||||
get { return CallbackOrder; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ccaf444d0d4f4cadb5debe7c41b20c4
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPreProcessAndroid.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AppLovinBuildPreProcessiOS.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Jonathan Liu on 10/17/2023.
|
||||
// Copyright © 2023 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_IOS
|
||||
|
||||
using System.Xml.Linq;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
public class AppLovinPreProcessiOS : AppLovinPreProcess, IPreprocessBuildWithReport
|
||||
{
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
AddGoogleCmpDependencyIfNeeded();
|
||||
}
|
||||
|
||||
private const string ElementNameIosPods = "iosPods";
|
||||
private const string ElementNameIosPod = "iosPod";
|
||||
private const string AttributeNameName = "name";
|
||||
private const string AttributeNameVersion = "version";
|
||||
private const string UmpDependencyPod = "GoogleUserMessagingPlatform";
|
||||
private const string UmpDependencyVersion = "~> 3.1";
|
||||
|
||||
private static void AddGoogleCmpDependencyIfNeeded()
|
||||
{
|
||||
if (AppLovinInternalSettings.Instance.ConsentFlowEnabled)
|
||||
{
|
||||
var umpDependency = new XElement(ElementNameIosPod,
|
||||
new XAttribute(AttributeNameName, UmpDependencyPod),
|
||||
new XAttribute(AttributeNameVersion, UmpDependencyVersion));
|
||||
var success = AddOrUpdateIosDependency(UmpDependencyPod, umpDependency);
|
||||
if (!success)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Google CMP will not function. Unable to add GoogleUserMessagingPlatform dependency.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveIosDependency(UmpDependencyPod);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates an iOS pod in the AppLovin Dependencies.xml file.
|
||||
/// </summary>
|
||||
/// <param name="pod">The pod that we are trying to update</param>
|
||||
/// <param name="newDependency">The new dependency to add if it doesn't exist</param>
|
||||
/// <returns>Returns true if the file was successfully edited</returns>
|
||||
private static bool AddOrUpdateIosDependency(string pod, XElement newDependency)
|
||||
{
|
||||
var dependenciesFilePath = AppLovinDependenciesFilePath;
|
||||
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath, AppLovinIntegrationManager.IsPluginInPackageManager);
|
||||
if (dependenciesDocument == null) return false;
|
||||
|
||||
AddOrUpdateDependency(dependenciesDocument,
|
||||
ElementNameIosPods,
|
||||
ElementNameIosPod,
|
||||
AttributeNameName,
|
||||
pod,
|
||||
newDependency);
|
||||
return SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removed an iOS pod from the AppLovin Dependencies.xml file.
|
||||
/// </summary>
|
||||
/// <param name="pod">The pod to remove</param>
|
||||
private static void RemoveIosDependency(string pod)
|
||||
{
|
||||
var dependenciesFilePath = AppLovinDependenciesFilePath;
|
||||
var dependenciesDocument = GetAppLovinDependenciesFile(dependenciesFilePath);
|
||||
if (dependenciesDocument == null) return;
|
||||
|
||||
var removed = RemoveDependency(dependenciesDocument,
|
||||
ElementNameIosPods,
|
||||
ElementNameIosPod,
|
||||
AttributeNameName,
|
||||
pod);
|
||||
|
||||
if (!removed) return;
|
||||
|
||||
SaveDependenciesFile(dependenciesDocument, dependenciesFilePath);
|
||||
}
|
||||
|
||||
public int callbackOrder
|
||||
{
|
||||
get { return CallbackOrder; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee45537a5833240d7afcfac4a38df1b9
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPreProcessiOS.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,658 @@
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 9/3/19.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_ANDROID
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using AppLovinMax.Internal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.PlayerLoop;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public class AppLovinQualityServiceData
|
||||
{
|
||||
// ReSharper disable once InconsistentNaming - Need to keep name for response data
|
||||
public string api_key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates the AppLovin Quality Service plugin to the provided build.gradle file.
|
||||
/// If the gradle file already has the plugin, the API key is updated.
|
||||
/// </summary>
|
||||
public abstract class AppLovinProcessGradleBuildFile : AppLovinPreProcess
|
||||
{
|
||||
private static readonly Regex TokenBuildScriptRepositories = new Regex(".*repositories.*");
|
||||
private static readonly Regex TokenBuildScriptDependencies = new Regex(".*classpath \'com.android.tools.build:gradle.*");
|
||||
private static readonly Regex TokenApplicationPlugin = new Regex(".*apply plugin: \'com.android.application\'.*");
|
||||
private static readonly Regex TokenApiKey = new Regex(".*apiKey.*");
|
||||
private static readonly Regex TokenAppLovinPlugin = new Regex(".*apply plugin:.+?(?=applovin-quality-service).*");
|
||||
|
||||
private const string PluginsMatcher = "plugins";
|
||||
private const string PluginManagementMatcher = "pluginManagement";
|
||||
private const string QualityServicePluginRoot = " id 'com.applovin.quality' version '+' apply false // NOTE: Requires version 4.8.3+ for Gradle version 7.2+";
|
||||
|
||||
private const string BuildScriptMatcher = "buildscript";
|
||||
private const string QualityServiceMavenRepo = "maven { url 'https://artifacts.applovin.com/android'; content { includeGroupByRegex 'com.applovin.*' } }";
|
||||
private const string QualityServiceDependencyClassPath = "classpath 'com.applovin.quality:AppLovinQualityServiceGradlePlugin:+'";
|
||||
private const string QualityServiceApplyPlugin = "apply plugin: 'applovin-quality-service'";
|
||||
private const string QualityServicePlugin = "applovin {";
|
||||
private const string QualityServiceApiKey = " apiKey '{0}'";
|
||||
private const string QualityServiceBintrayMavenRepo = "https://applovin.bintray.com/Quality-Service";
|
||||
private const string QualityServiceNoRegexMavenRepo = "maven { url 'https://artifacts.applovin.com/android' }";
|
||||
|
||||
// Legacy plugin detection variables
|
||||
private const string QualityServiceDependencyClassPathV3 = "classpath 'com.applovin.quality:AppLovinQualityServiceGradlePlugin:3.+'";
|
||||
private static readonly Regex TokenSafeDkLegacyApplyPlugin = new Regex(".*apply plugin:.+?(?=safedk).*");
|
||||
private const string SafeDkLegacyPlugin = "safedk {";
|
||||
private const string SafeDkLegacyMavenRepo = "http://download.safedk.com";
|
||||
private const string SafeDkLegacyDependencyClassPath = "com.safedk:SafeDKGradlePlugin:";
|
||||
|
||||
/// <summary>
|
||||
/// Adds the Quality Service plugin to the root gradle file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the unityLibrary's module.</param>
|
||||
/// <returns>True if the plugin was added successfully, otherwise return false</returns>
|
||||
protected static bool AddQualityServiceToRootGradleFile(string path)
|
||||
{
|
||||
var rootGradleBuildFilePath = Path.Combine(path, "../build.gradle");
|
||||
var shouldAddQualityServiceToDependencies = ShouldAddQualityServiceToDependencies(rootGradleBuildFilePath);
|
||||
|
||||
if (shouldAddQualityServiceToDependencies)
|
||||
{
|
||||
// Add the Quality Service Plugin to the dependencies block in the root build.gradle file
|
||||
return AddQualityServiceBuildScriptLines(rootGradleBuildFilePath);
|
||||
}
|
||||
|
||||
// Add the Quality Service Plugin to the plugin block in the root build.gradle file
|
||||
var rootSettingsGradleFilePath = Path.Combine(path, "../settings.gradle");
|
||||
var qualityServiceAdded = AddPluginToRootGradleBuildFile(rootGradleBuildFilePath);
|
||||
var appLovinRepositoryAdded = AddAppLovinRepository(rootSettingsGradleFilePath);
|
||||
return qualityServiceAdded && appLovinRepositoryAdded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the AppLovin Quality Service plugin should be added to the
|
||||
/// dependencies block in the root build.gradle file or to the plugins block.
|
||||
///
|
||||
/// Gradle's required structure for including plugins varies by version:
|
||||
/// - Older versions of Gradle require the plugin to be added to the dependencies block.
|
||||
/// Example:
|
||||
/// dependencies {
|
||||
/// classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
/// classpath 'com.applovin.quality:AppLovinQualityServiceGradlePlugin:+'
|
||||
/// }
|
||||
///
|
||||
/// - Newer versions of gradle require the plugin to be added to the plugins block.
|
||||
/// Example:
|
||||
/// plugins {
|
||||
/// id 'com.android.application' version '7.4.2' apply false
|
||||
/// id 'com.android.library' version '7.4.2' apply false
|
||||
/// id 'com.applovin.quality' version '+' apply false
|
||||
/// }
|
||||
///
|
||||
/// Since Unity projects may use custom Gradle versions depending on the Unity version or
|
||||
/// user modifications, this check ensures proper integration of the AppLovin plugin.
|
||||
/// </summary>
|
||||
/// <param name="rootGradleBuildFile">The path to project's root build.gradle file.</param>
|
||||
/// <returns><c>true</c> if the file contains a `dependencies` block, indicating an older Gradle version</returns>
|
||||
private static bool ShouldAddQualityServiceToDependencies(string rootGradleBuildFile)
|
||||
{
|
||||
var lines = File.ReadAllLines(rootGradleBuildFile).ToList();
|
||||
return lines.Any(line => TokenBuildScriptDependencies.IsMatch(line));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the provided Gradle script to add Quality Service plugin.
|
||||
/// </summary>
|
||||
/// <param name="applicationGradleBuildFilePath">The gradle file to update.</param>
|
||||
protected static void AddAppLovinQualityServicePlugin(string applicationGradleBuildFilePath)
|
||||
{
|
||||
if (!AppLovinSettings.Instance.QualityServiceEnabled) return;
|
||||
|
||||
var sdkKey = AppLovinSettings.Instance.SdkKey;
|
||||
if (string.IsNullOrEmpty(sdkKey))
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. SDK Key is empty. Please enter the AppLovin SDK Key in the Integration Manager.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the API Key using the SDK Key.
|
||||
var qualityServiceData = RetrieveQualityServiceData(sdkKey);
|
||||
var apiKey = qualityServiceData.api_key;
|
||||
if (string.IsNullOrEmpty(apiKey))
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. API Key is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate the updated Gradle file that needs to be written.
|
||||
var lines = File.ReadAllLines(applicationGradleBuildFilePath).ToList();
|
||||
var sanitizedLines = RemoveLegacySafeDkPlugin(lines);
|
||||
var outputLines = GenerateUpdatedBuildFileLines(
|
||||
sanitizedLines,
|
||||
apiKey,
|
||||
false // The buildscript closure related lines will to be added to the root build.gradle file.
|
||||
);
|
||||
// outputLines can be null if we couldn't add the plugin.
|
||||
if (outputLines == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(applicationGradleBuildFilePath, string.Join("\n", outputLines.ToArray()) + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. Gradle file write failed.");
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds AppLovin Quality Service plugin DSL element to the project's root build.gradle file.
|
||||
/// Sample build.gradle file after adding quality service:
|
||||
/// plugins {
|
||||
/// id 'com.android.application' version '7.4.2' apply false
|
||||
/// id 'com.android.library' version '7.4.2' apply false
|
||||
/// id 'com.applovin.quality' version '+' apply false
|
||||
/// }
|
||||
/// tasks.register('clean', Delete) {
|
||||
/// delete rootProject.layout.buildDirectory
|
||||
/// }
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rootGradleBuildFile">The path to project's root build.gradle file.</param>
|
||||
/// <returns><c>true</c> when the plugin was added successfully.</returns>
|
||||
private static bool AddPluginToRootGradleBuildFile(string rootGradleBuildFile)
|
||||
{
|
||||
var lines = File.ReadAllLines(rootGradleBuildFile).ToList();
|
||||
|
||||
// Check if the plugin is already added to the file.
|
||||
var pluginAdded = lines.Any(line => line.Contains(QualityServicePluginRoot));
|
||||
if (pluginAdded) return true;
|
||||
|
||||
var outputLines = new List<string>();
|
||||
var insidePluginsClosure = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains(PluginsMatcher))
|
||||
{
|
||||
insidePluginsClosure = true;
|
||||
}
|
||||
|
||||
if (!pluginAdded && insidePluginsClosure && line.Contains("}"))
|
||||
{
|
||||
outputLines.Add(QualityServicePluginRoot);
|
||||
pluginAdded = true;
|
||||
insidePluginsClosure = false;
|
||||
}
|
||||
|
||||
outputLines.Add(line);
|
||||
}
|
||||
|
||||
if (!pluginAdded) return false;
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(rootGradleBuildFile, string.Join("\n", outputLines.ToArray()) + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. Root Gradle file write failed.");
|
||||
Console.WriteLine(exception);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the AppLovin maven repository to the project's settings.gradle file.
|
||||
/// Sample settings.gradle file after adding AppLovin Repository:
|
||||
/// pluginManagement {
|
||||
/// repositories {
|
||||
/// maven { url 'https://artifacts.applovin.com/android'; content { includeGroupByRegex 'com.applovin.*' } }
|
||||
///
|
||||
/// gradlePluginPortal()
|
||||
/// google()
|
||||
/// mavenCentral()
|
||||
/// }
|
||||
/// }
|
||||
/// ...
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="settingsGradleFile">The path to the project's settings.gradle file.</param>
|
||||
/// <returns><c>true</c> if the repository was added successfully.</returns>
|
||||
private static bool AddAppLovinRepository(string settingsGradleFile)
|
||||
{
|
||||
var lines = File.ReadLines(settingsGradleFile).ToList();
|
||||
var outputLines = new List<string>();
|
||||
var mavenRepoAdded = false;
|
||||
var pluginManagementClosureDepth = 0;
|
||||
var insidePluginManagementClosure = false;
|
||||
var pluginManagementMatched = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
outputLines.Add(line);
|
||||
|
||||
if (!pluginManagementMatched && line.Contains(PluginManagementMatcher))
|
||||
{
|
||||
pluginManagementMatched = true;
|
||||
insidePluginManagementClosure = true;
|
||||
}
|
||||
|
||||
if (insidePluginManagementClosure)
|
||||
{
|
||||
if (line.Contains("{"))
|
||||
{
|
||||
pluginManagementClosureDepth++;
|
||||
}
|
||||
|
||||
if (line.Contains("}"))
|
||||
{
|
||||
pluginManagementClosureDepth--;
|
||||
}
|
||||
|
||||
if (pluginManagementClosureDepth == 0)
|
||||
{
|
||||
insidePluginManagementClosure = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (insidePluginManagementClosure)
|
||||
{
|
||||
if (!mavenRepoAdded && TokenBuildScriptRepositories.IsMatch(line))
|
||||
{
|
||||
outputLines.Add(GetFormattedBuildScriptLine(QualityServiceMavenRepo));
|
||||
mavenRepoAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mavenRepoAdded) return false;
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(settingsGradleFile, string.Join("\n", outputLines.ToArray()) + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. Setting Gradle file write failed.");
|
||||
Console.WriteLine(exception);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the necessary AppLovin Quality Service dependency and maven repo lines to the provided root build.gradle file.
|
||||
/// Sample build.gradle file after adding quality service:
|
||||
/// allprojects {
|
||||
/// buildscript {
|
||||
/// repositories {
|
||||
/// maven { url 'https://artifacts.applovin.com/android'; content { includeGroupByRegex 'com.applovin.*' } }
|
||||
/// google()
|
||||
/// jcenter()
|
||||
/// }
|
||||
///
|
||||
/// dependencies {
|
||||
/// classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
/// classpath 'com.applovin.quality:AppLovinQualityServiceGradlePlugin:+'
|
||||
/// }
|
||||
/// ...
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="rootGradleBuildFile">The root build.gradle file path</param>
|
||||
/// <returns><c>true</c> if the build script lines were applied correctly.</returns>
|
||||
private static bool AddQualityServiceBuildScriptLines(string rootGradleBuildFile)
|
||||
{
|
||||
var lines = File.ReadAllLines(rootGradleBuildFile).ToList();
|
||||
var outputLines = GenerateUpdatedBuildFileLines(lines, null, true);
|
||||
|
||||
// outputLines will be null if we couldn't add the build script lines.
|
||||
if (outputLines == null) return false;
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(rootGradleBuildFile, string.Join("\n", outputLines.ToArray()) + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. Root Gradle file write failed.");
|
||||
Console.WriteLine(exception);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the AppLovin Quality Service Plugin or Legacy SafeDK plugin from the given gradle template file if either of them are present.
|
||||
/// </summary>
|
||||
/// <param name="gradleTemplateFile">The gradle template file from which to remove the plugin from</param>
|
||||
protected static void RemoveAppLovinQualityServiceOrSafeDkPlugin(string gradleTemplateFile)
|
||||
{
|
||||
var lines = File.ReadAllLines(gradleTemplateFile).ToList();
|
||||
lines = RemoveLegacySafeDkPlugin(lines);
|
||||
lines = RemoveAppLovinQualityServicePlugin(lines);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(gradleTemplateFile, string.Join("\n", lines.ToArray()) + "\n");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to remove AppLovin Quality Service Plugin from mainTemplate.gradle. Please remove the Quality Service plugin from the mainTemplate.gradle manually.");
|
||||
Console.WriteLine(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static AppLovinQualityServiceData RetrieveQualityServiceData(string sdkKey)
|
||||
{
|
||||
var webRequestConfig = new WebRequestConfig()
|
||||
{
|
||||
JsonString = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey),
|
||||
EndPoint = "https://api2.safedk.com/v1/build/cred",
|
||||
RequestType = WebRequestType.Post,
|
||||
};
|
||||
|
||||
webRequestConfig.Headers.Add("Content-Type", "application/json");
|
||||
|
||||
var maxWebRequest = new MaxWebRequest(webRequestConfig);
|
||||
var webResponse = maxWebRequest.SendSync();
|
||||
|
||||
if (!webResponse.IsSuccess)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to retrieve API Key for SDK Key: " + sdkKey + "with error: " + webResponse.ErrorMessage);
|
||||
return new AppLovinQualityServiceData();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonUtility.FromJson<AppLovinQualityServiceData>(webResponse.ResponseMessage);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Failed to parse API Key." + exception);
|
||||
return new AppLovinQualityServiceData();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> RemoveLegacySafeDkPlugin(List<string> lines)
|
||||
{
|
||||
return RemovePlugin(lines, SafeDkLegacyPlugin, SafeDkLegacyMavenRepo, SafeDkLegacyDependencyClassPath, TokenSafeDkLegacyApplyPlugin);
|
||||
}
|
||||
|
||||
private static List<string> RemoveAppLovinQualityServicePlugin(List<string> lines)
|
||||
{
|
||||
return RemovePlugin(lines, QualityServicePlugin, QualityServiceMavenRepo, QualityServiceDependencyClassPath, TokenAppLovinPlugin);
|
||||
}
|
||||
|
||||
private static List<string> RemovePlugin(List<string> lines, string pluginLine, string mavenRepo, string dependencyClassPath, Regex applyPluginToken)
|
||||
{
|
||||
var sanitizedLines = new List<string>();
|
||||
var legacyRepoRemoved = false;
|
||||
var legacyDependencyClassPathRemoved = false;
|
||||
var legacyPluginRemoved = false;
|
||||
var legacyPluginMatched = false;
|
||||
var insideLegacySafeDkClosure = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!legacyPluginMatched && line.Contains(pluginLine))
|
||||
{
|
||||
legacyPluginMatched = true;
|
||||
insideLegacySafeDkClosure = true;
|
||||
}
|
||||
|
||||
if (insideLegacySafeDkClosure && line.Contains("}"))
|
||||
{
|
||||
insideLegacySafeDkClosure = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (insideLegacySafeDkClosure)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!legacyRepoRemoved && line.Contains(mavenRepo))
|
||||
{
|
||||
legacyRepoRemoved = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!legacyDependencyClassPathRemoved && line.Contains(dependencyClassPath))
|
||||
{
|
||||
legacyDependencyClassPathRemoved = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!legacyPluginRemoved && applyPluginToken.IsMatch(line))
|
||||
{
|
||||
legacyPluginRemoved = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
sanitizedLines.Add(line);
|
||||
}
|
||||
|
||||
return sanitizedLines;
|
||||
}
|
||||
|
||||
private static List<string> GenerateUpdatedBuildFileLines(List<string> lines, string apiKey, bool addBuildScriptLines)
|
||||
{
|
||||
// Check if the plugin exists, if so, update the SDK Key.
|
||||
var pluginExists = lines.Any(line => TokenAppLovinPlugin.IsMatch(line));
|
||||
return pluginExists ? UpdateExistingPlugin(lines, apiKey) : AddPluginAndBuildScript(lines, apiKey, addBuildScriptLines);
|
||||
}
|
||||
|
||||
private static List<string> UpdateExistingPlugin(List<string> lines, string apiKey)
|
||||
{
|
||||
// A sample of the template file.
|
||||
// ...
|
||||
// allprojects {
|
||||
// repositories {**ARTIFACTORYREPOSITORY**
|
||||
// google()
|
||||
// jcenter()
|
||||
// flatDir {
|
||||
// dirs 'libs'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// apply plugin: 'com.android.application'
|
||||
// **APPLY_PLUGINS**
|
||||
//
|
||||
// dependencies {
|
||||
// implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// **DEPS**}
|
||||
// ...
|
||||
var outputLines = new List<string>();
|
||||
var pluginMatched = false;
|
||||
var insideAppLovinClosure = false;
|
||||
var updatedApiKey = false;
|
||||
var mavenRepoUpdated = false;
|
||||
var dependencyClassPathUpdated = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
// Bintray maven repo is no longer being used. Update to s3 maven repo with regex check
|
||||
if (!mavenRepoUpdated && (line.Contains(QualityServiceBintrayMavenRepo) || line.Contains(QualityServiceNoRegexMavenRepo)))
|
||||
{
|
||||
outputLines.Add(GetFormattedBuildScriptLine(QualityServiceMavenRepo));
|
||||
mavenRepoUpdated = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// We no longer use version specific dependency class path. Just use + for version to always pull the latest.
|
||||
if (!dependencyClassPathUpdated && line.Contains(QualityServiceDependencyClassPathV3))
|
||||
{
|
||||
outputLines.Add(GetFormattedBuildScriptLine(QualityServiceDependencyClassPath));
|
||||
dependencyClassPathUpdated = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pluginMatched && line.Contains(QualityServicePlugin))
|
||||
{
|
||||
insideAppLovinClosure = true;
|
||||
pluginMatched = true;
|
||||
}
|
||||
|
||||
if (insideAppLovinClosure && line.Contains("}"))
|
||||
{
|
||||
insideAppLovinClosure = false;
|
||||
}
|
||||
|
||||
// Update the API key.
|
||||
if (insideAppLovinClosure && !updatedApiKey && TokenApiKey.IsMatch(line))
|
||||
{
|
||||
outputLines.Add(string.Format(QualityServiceApiKey, apiKey));
|
||||
updatedApiKey = true;
|
||||
}
|
||||
// Keep adding the line until we find and update the plugin.
|
||||
else
|
||||
{
|
||||
outputLines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
return outputLines;
|
||||
}
|
||||
|
||||
private static List<string> AddPluginAndBuildScript(List<string> lines, string apiKey, bool addBuildScriptLines)
|
||||
{
|
||||
var shouldAddPlugin = MaxSdkUtils.IsValidString(apiKey);
|
||||
if (shouldAddPlugin)
|
||||
{
|
||||
lines = AddPlugin(lines, apiKey);
|
||||
if (lines == null) return null;
|
||||
}
|
||||
|
||||
if (!addBuildScriptLines) return lines;
|
||||
|
||||
lines = AddBuildScript(lines);
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static List<string> AddBuildScript(List<string> lines)
|
||||
{
|
||||
var outputLines = new List<string>();
|
||||
var buildScriptClosureDepth = 0;
|
||||
var insideBuildScriptClosure = false;
|
||||
var buildScriptMatched = false;
|
||||
var qualityServiceRepositoryAdded = false;
|
||||
var qualityServiceDependencyClassPathAdded = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
// Add the line to the output lines.
|
||||
outputLines.Add(line);
|
||||
|
||||
if (!buildScriptMatched && line.Contains(BuildScriptMatcher))
|
||||
{
|
||||
buildScriptMatched = true;
|
||||
insideBuildScriptClosure = true;
|
||||
}
|
||||
|
||||
// Match the parenthesis to track if we are still inside the buildscript closure.
|
||||
if (insideBuildScriptClosure)
|
||||
{
|
||||
if (line.Contains("{"))
|
||||
{
|
||||
buildScriptClosureDepth++;
|
||||
}
|
||||
|
||||
if (line.Contains("}"))
|
||||
{
|
||||
buildScriptClosureDepth--;
|
||||
}
|
||||
|
||||
if (buildScriptClosureDepth == 0)
|
||||
{
|
||||
insideBuildScriptClosure = false;
|
||||
|
||||
// There may be multiple buildscript closures and we need to keep looking until we added both the repository and classpath.
|
||||
buildScriptMatched = qualityServiceRepositoryAdded && qualityServiceDependencyClassPathAdded;
|
||||
}
|
||||
}
|
||||
|
||||
if (insideBuildScriptClosure)
|
||||
{
|
||||
// Add the build script dependency repositories.
|
||||
if (!qualityServiceRepositoryAdded && TokenBuildScriptRepositories.IsMatch(line))
|
||||
{
|
||||
outputLines.Add(GetFormattedBuildScriptLine(QualityServiceMavenRepo));
|
||||
qualityServiceRepositoryAdded = true;
|
||||
}
|
||||
// Add the build script dependencies.
|
||||
else if (!qualityServiceDependencyClassPathAdded && TokenBuildScriptDependencies.IsMatch(line))
|
||||
{
|
||||
outputLines.Add(GetFormattedBuildScriptLine(QualityServiceDependencyClassPath));
|
||||
qualityServiceDependencyClassPathAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!qualityServiceRepositoryAdded || !qualityServiceDependencyClassPathAdded)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return outputLines;
|
||||
}
|
||||
|
||||
private static List<string> AddPlugin(List<string> lines, string apiKey)
|
||||
{
|
||||
var outputLines = new List<string>();
|
||||
var qualityServicePluginAdded = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
outputLines.Add(line);
|
||||
|
||||
// Add the plugin.
|
||||
if (qualityServicePluginAdded || !TokenApplicationPlugin.IsMatch(line)) continue;
|
||||
|
||||
outputLines.Add(QualityServiceApplyPlugin);
|
||||
outputLines.AddRange(GenerateAppLovinPluginClosure(apiKey));
|
||||
qualityServicePluginAdded = true;
|
||||
}
|
||||
|
||||
return qualityServicePluginAdded ? outputLines : null;
|
||||
}
|
||||
|
||||
public static string GetFormattedBuildScriptLine(string buildScriptLine)
|
||||
{
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
return " "
|
||||
#else
|
||||
return " "
|
||||
#endif
|
||||
+ buildScriptLine;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GenerateAppLovinPluginClosure(string apiKey)
|
||||
{
|
||||
// applovin {
|
||||
// // NOTE: DO NOT CHANGE - this is NOT your AppLovin MAX SDK key - this is a derived key.
|
||||
// apiKey "456...a1b"
|
||||
// }
|
||||
var linesToInject = new List<string>(5);
|
||||
linesToInject.Add("");
|
||||
linesToInject.Add("applovin {");
|
||||
linesToInject.Add(" // NOTE: DO NOT CHANGE - this is NOT your AppLovin MAX SDK key - this is a derived key.");
|
||||
linesToInject.Add(string.Format(QualityServiceApiKey, apiKey));
|
||||
linesToInject.Add("}");
|
||||
|
||||
return linesToInject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 732b7510bc9c94aafb3fd3b8c0dc5d2d
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinProcessGradleBuildFile.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// AppLovinSettings.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 1/27/20.
|
||||
// Copyright © 2019 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using AppLovinMax.Scripts.IntegrationManager.Editor;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ScriptableObject"/> representing the AppLovin Settings that can be set in the Integration Manager Window.
|
||||
///
|
||||
/// The scriptable object asset is created with the name <c>AppLovinSettings.asset</c> and is placed under the directory <c>Assets/MaxSdk/Resources</c>.
|
||||
///
|
||||
/// NOTE: Not name spacing this class since it is reflected upon by the Google adapter and will break compatibility.
|
||||
/// </summary>
|
||||
public class AppLovinSettings : ScriptableObject
|
||||
{
|
||||
private const string SettingsExportPath = "MaxSdk/Resources/AppLovinSettings.asset";
|
||||
|
||||
private static AppLovinSettings instance;
|
||||
|
||||
[SerializeField] private bool qualityServiceEnabled = true;
|
||||
[SerializeField] private string sdkKey;
|
||||
|
||||
[SerializeField] private string customGradleVersionUrl;
|
||||
[SerializeField] private string customGradleToolsVersion;
|
||||
|
||||
[SerializeField] private string adMobAndroidAppId = string.Empty;
|
||||
[SerializeField] private string adMobIosAppId = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// An instance of AppLovin Setting.
|
||||
/// </summary>
|
||||
public static AppLovinSettings Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
// Check for an existing AppLovinSettings somewhere in the project
|
||||
var guids = AssetDatabase.FindAssets("AppLovinSettings t:ScriptableObject");
|
||||
if (guids.Length > 1)
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Multiple AppLovinSettings found. This may cause unexpected results.");
|
||||
}
|
||||
|
||||
if (guids.Length != 0)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
instance = AssetDatabase.LoadAssetAtPath<AppLovinSettings>(path);
|
||||
return instance;
|
||||
}
|
||||
|
||||
string settingsFilePath;
|
||||
// The settings file should be under the Assets/ folder so that it can be version controlled and cannot be overriden when updating.
|
||||
// If the plugin is outside the Assets folder or if there is no existing AppLovinSettings asset, create the settings asset at the default location.
|
||||
if (AppLovinIntegrationManager.IsPluginInPackageManager)
|
||||
{
|
||||
// Note: Can't use absolute path when calling `CreateAsset`. Should use path relative to Assets/ directory.
|
||||
settingsFilePath = MaxSdkUtils.NormalizeToUnityPath(Path.Combine("Assets", SettingsExportPath));
|
||||
|
||||
var maxSdkDir = MaxSdkUtils.NormalizeToUnityPath(Path.Combine(Application.dataPath, "MaxSdk"));
|
||||
if (!Directory.Exists(maxSdkDir))
|
||||
{
|
||||
Directory.CreateDirectory(maxSdkDir);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsFilePath = MaxSdkUtils.NormalizeToUnityPath(Path.Combine(AppLovinIntegrationManager.PluginParentDirectory, SettingsExportPath));
|
||||
}
|
||||
|
||||
var settingsDir = Path.GetDirectoryName(settingsFilePath);
|
||||
if (!Directory.Exists(settingsDir))
|
||||
{
|
||||
Directory.CreateDirectory(settingsDir);
|
||||
}
|
||||
|
||||
// On script reload AssetDatabase.FindAssets() can fail and will overwrite AppLovinSettings without this check
|
||||
if (!File.Exists(settingsFilePath))
|
||||
{
|
||||
instance = CreateInstance<AppLovinSettings>();
|
||||
AssetDatabase.CreateAsset(instance, settingsFilePath);
|
||||
MaxSdkLogger.D("Creating new AppLovinSettings asset at path: " + settingsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to install Quality Service plugin.
|
||||
/// </summary>
|
||||
public bool QualityServiceEnabled
|
||||
{
|
||||
get { return Instance.qualityServiceEnabled; }
|
||||
set { Instance.qualityServiceEnabled = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AppLovin SDK Key.
|
||||
/// </summary>
|
||||
public string SdkKey
|
||||
{
|
||||
get { return Instance.sdkKey; }
|
||||
set { Instance.sdkKey = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A URL to set the distributionUrl in the gradle-wrapper.properties file (ex: https\://services.gradle.org/distributions/gradle-6.9.3-bin.zip)
|
||||
/// </summary>
|
||||
public string CustomGradleVersionUrl
|
||||
{
|
||||
get { return Instance.customGradleVersionUrl; }
|
||||
set { Instance.customGradleVersionUrl = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A string to set the custom gradle tools version (ex: com.android.tools.build:gradle:4.2.0)
|
||||
/// </summary>
|
||||
public string CustomGradleToolsVersion
|
||||
{
|
||||
get { return Instance.customGradleToolsVersion; }
|
||||
set { Instance.customGradleToolsVersion = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AdMob Android App ID.
|
||||
/// </summary>
|
||||
public string AdMobAndroidAppId
|
||||
{
|
||||
get { return Instance.adMobAndroidAppId; }
|
||||
set { Instance.adMobAndroidAppId = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AdMob iOS App ID.
|
||||
/// </summary>
|
||||
public string AdMobIosAppId
|
||||
{
|
||||
get { return Instance.adMobIosAppId; }
|
||||
set { Instance.adMobIosAppId = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the instance of the settings.
|
||||
/// </summary>
|
||||
public void SaveAsync()
|
||||
{
|
||||
EditorUtility.SetDirty(instance);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebc0ba1b5ef6b4a6b9dd53d7eadfea16
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinSettings.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using AppLovinMax.ThirdParty.MiniJson;
|
||||
|
||||
namespace AppLovinMax.Scripts.IntegrationManager.Editor
|
||||
{
|
||||
public class AppLovinUpmManifest
|
||||
{
|
||||
private const string KeyUrl = "url";
|
||||
private const string KeyName = "name";
|
||||
private const string KeyScopes = "scopes";
|
||||
private const string KeyScopedRegistry = "scopedRegistries";
|
||||
|
||||
private Dictionary<string, object> manifest;
|
||||
|
||||
private static string ManifestPath
|
||||
{
|
||||
get { return Path.Combine(Directory.GetCurrentDirectory(), "Packages/manifest.json"); }
|
||||
}
|
||||
|
||||
// Private constructor to enforce the use of the Load() method
|
||||
private AppLovinUpmManifest() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of AppLovinUpmManifest and loads the manifest.json file.
|
||||
/// </summary>
|
||||
/// <returns>An instance of AppLovinUpmManifest</returns>
|
||||
public static AppLovinUpmManifest Load()
|
||||
{
|
||||
return new AppLovinUpmManifest { manifest = GetManifest() };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds or updates a scoped registry in the manifest.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the registry</param>
|
||||
/// <param name="url">The url of the registry</param>
|
||||
/// <param name="scopes">The scopes of the registry</param>
|
||||
public void AddOrUpdateRegistry(string name, string url, List<string> scopes)
|
||||
{
|
||||
var registry = GetRegistry(name);
|
||||
if (registry == null)
|
||||
{
|
||||
var registries = GetRegistries();
|
||||
if (registries == null) return;
|
||||
|
||||
registries.Add(new Dictionary<string, object>
|
||||
{
|
||||
{KeyName, name},
|
||||
{KeyUrl, url},
|
||||
{KeyScopes, scopes}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateRegistry(registry, scopes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the manifest by serializing it back to JSON and writing to file.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
var content = Json.Serialize(manifest, true);
|
||||
File.WriteAllText(ManifestPath, content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a dependency to the manifest.
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the package to add</param>
|
||||
/// <param name="version">The version of the package to add</param>
|
||||
public void AddPackageDependency(string packageName, string version)
|
||||
{
|
||||
var manifestDependencies = GetDependencies();
|
||||
manifestDependencies[packageName] = version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a dependency from the manifest.
|
||||
/// </summary>
|
||||
/// <param name="packageName">The name of the package to remove</param>
|
||||
public void RemovePackageDependency(string packageName)
|
||||
{
|
||||
var manifestDependencies = GetDependencies();
|
||||
manifestDependencies.Remove(packageName);
|
||||
}
|
||||
|
||||
#region Utility
|
||||
|
||||
/// <summary>
|
||||
/// Returns the manifest.json file as a dictionary.
|
||||
/// </summary>
|
||||
private static Dictionary<string, object> GetManifest()
|
||||
{
|
||||
if (!File.Exists(ManifestPath))
|
||||
{
|
||||
throw new Exception("Manifest not Found!");
|
||||
}
|
||||
|
||||
var manifestJson = File.ReadAllText(ManifestPath);
|
||||
if (string.IsNullOrEmpty(manifestJson))
|
||||
{
|
||||
throw new Exception("Manifest is empty!");
|
||||
}
|
||||
|
||||
var deserializedManifest = Json.Deserialize(manifestJson) as Dictionary<string, object>;
|
||||
if (deserializedManifest == null)
|
||||
{
|
||||
throw new Exception("Failed to deserialize manifest");
|
||||
}
|
||||
|
||||
return deserializedManifest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the manifest's dependencies section.
|
||||
/// </summary>
|
||||
/// <returns>The dependencies section of the manifest.</returns>
|
||||
private Dictionary<string, object> GetDependencies()
|
||||
{
|
||||
var dependencies = manifest["dependencies"] as Dictionary<string, object>;
|
||||
if (dependencies == null)
|
||||
{
|
||||
throw new Exception("No dependencies found in manifest.");
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the manifest's registries section. Creates a new registries section if one does not exist.
|
||||
/// </summary>
|
||||
/// <returns>The registries section of the manifest.</returns>
|
||||
private List<object> GetRegistries()
|
||||
{
|
||||
EnsureScopedRegistryExists();
|
||||
return manifest[KeyScopedRegistry] as List<object>;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a scoped registry with the given name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the registry</param>
|
||||
/// <returns>Returns the registry, or null if it can't be found</returns>
|
||||
private Dictionary<string, object> GetRegistry(string name)
|
||||
{
|
||||
var registries = GetRegistries();
|
||||
if (registries == null) return null;
|
||||
|
||||
return registries
|
||||
.OfType<Dictionary<string, object>>()
|
||||
.FirstOrDefault(registry => MaxSdkUtils.GetStringFromDictionary(registry, KeyName).Equals(name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the section for scoped registries in the manifest.json file if it doesn't exist.
|
||||
/// </summary>
|
||||
private void EnsureScopedRegistryExists()
|
||||
{
|
||||
if (manifest.ContainsKey(KeyScopedRegistry)) return;
|
||||
|
||||
manifest.Add(KeyScopedRegistry, new List<object>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a registry to make sure it contains the new scopes.
|
||||
/// </summary>
|
||||
/// <param name="registry">The registry to update</param>
|
||||
/// <param name="newScopes">The scopes we want added to the registry</param>
|
||||
private static void UpdateRegistry(Dictionary<string, object> registry, List<string> newScopes)
|
||||
{
|
||||
var scopes = MaxSdkUtils.GetListFromDictionary(registry, KeyScopes);
|
||||
if (scopes == null)
|
||||
{
|
||||
registry[KeyScopes] = new List<string>(newScopes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add scopes that are not already in the list
|
||||
var uniqueNewScopes = newScopes.Where(scope => !scopes.Contains(scope)).ToList();
|
||||
scopes.AddRange(uniqueNewScopes);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40e2ad4a252104688b8627f82e806b2e
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/AppLovinUpmManifest.cs
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/AppLovinUpmManifest.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "MaxSdk.Scripts.IntegrationManager.Editor",
|
||||
"references": [
|
||||
"MaxSdk.Scripts"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a10a05a8449c42519fd80f2b8b580de3
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/IntegrationManager/Editor/MaxSdk.IntegrationManager.Editor.asmdef
|
||||
timeCreated: 1591749873
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// MaxCmpService.cs
|
||||
// AppLovin User Engagement Unity Plugin
|
||||
//
|
||||
// Created by Santosh Bagadi on 10/1/23.
|
||||
// Copyright © 2023 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#elif UNITY_ANDROID
|
||||
using UnityEngine;
|
||||
#elif UNITY_IOS
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// This class provides direct APIs for interfacing with the Google-certified CMP installed, if any.
|
||||
/// </summary>
|
||||
public class MaxCmpService
|
||||
{
|
||||
private static readonly MaxCmpService _instance = new MaxCmpService();
|
||||
|
||||
private MaxCmpService() { }
|
||||
|
||||
private static Action<MaxCmpError> _onCompletedAction;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#elif UNITY_ANDROID
|
||||
private static readonly AndroidJavaClass MaxUnityPluginClass = new AndroidJavaClass("com.applovin.mediation.unity.MaxUnityPlugin");
|
||||
#elif UNITY_IOS
|
||||
[DllImport("__Internal")]
|
||||
private static extern void _MaxShowCmpForExistingUser();
|
||||
|
||||
[DllImport("__Internal")]
|
||||
private static extern bool _MaxHasSupportedCmp();
|
||||
#endif
|
||||
|
||||
internal static MaxCmpService Instance
|
||||
{
|
||||
get { return _instance; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows the CMP flow to an existing user.
|
||||
/// Note that the user's current consent will be reset before the CMP alert is shown.
|
||||
/// </summary>
|
||||
/// <param name="onCompletedAction">Called when the CMP flow finishes showing.</param>
|
||||
public void ShowCmpForExistingUser(Action<MaxCmpError> onCompletedAction)
|
||||
{
|
||||
_onCompletedAction = onCompletedAction;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var errorProps = new Dictionary<string, object>
|
||||
{
|
||||
{"code", (int) MaxCmpError.ErrorCode.FormUnavailable},
|
||||
{"message", "CMP is not supported in Unity editor"}
|
||||
};
|
||||
|
||||
NotifyCompletedIfNeeded(errorProps);
|
||||
#elif UNITY_ANDROID
|
||||
MaxUnityPluginClass.CallStatic("showCmpForExistingUser");
|
||||
#elif UNITY_IOS
|
||||
_MaxShowCmpForExistingUser();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns <code>true</code> if a supported CMP SDK is detected.
|
||||
/// </summary>
|
||||
public bool HasSupportedCmp
|
||||
{
|
||||
get
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return false;
|
||||
#elif UNITY_ANDROID
|
||||
return MaxUnityPluginClass.CallStatic<bool>("hasSupportedCmp");
|
||||
#elif UNITY_IOS
|
||||
return _MaxHasSupportedCmp();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
internal static void NotifyCompletedIfNeeded(Dictionary<string, object> errorProps)
|
||||
{
|
||||
if (_onCompletedAction == null) return;
|
||||
|
||||
var error = (errorProps == null) ? null : MaxCmpError.Create(errorProps);
|
||||
_onCompletedAction(error);
|
||||
}
|
||||
}
|
||||
|
||||
public class MaxCmpError
|
||||
{
|
||||
public enum ErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that an unspecified error has occurred.
|
||||
/// </summary>
|
||||
Unspecified = -1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the CMP has not been integrated correctly.
|
||||
/// </summary>
|
||||
IntegrationError = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the CMP form is unavailable.
|
||||
/// </summary>
|
||||
FormUnavailable = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the CMP form is not required.
|
||||
/// </summary>
|
||||
FormNotRequired = 3
|
||||
}
|
||||
|
||||
public static MaxCmpError Create(IDictionary<string, object> error)
|
||||
{
|
||||
return new MaxCmpError()
|
||||
{
|
||||
Code = GetCode(MaxSdkUtils.GetIntFromDictionary(error, "code")),
|
||||
Message = MaxSdkUtils.GetStringFromDictionary(error, "message"),
|
||||
CmpCode = MaxSdkUtils.GetIntFromDictionary(error, "cmpCode", -1),
|
||||
CmpMessage = MaxSdkUtils.GetStringFromDictionary(error, "cmpMessage")
|
||||
};
|
||||
}
|
||||
|
||||
private static ErrorCode GetCode(int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 1:
|
||||
return ErrorCode.IntegrationError;
|
||||
case 2:
|
||||
return ErrorCode.FormUnavailable;
|
||||
case 3:
|
||||
return ErrorCode.FormNotRequired;
|
||||
default:
|
||||
return ErrorCode.Unspecified;
|
||||
}
|
||||
}
|
||||
|
||||
private MaxCmpError() { }
|
||||
|
||||
/// <summary>
|
||||
/// The error code for this error.
|
||||
/// </summary>
|
||||
public ErrorCode Code { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The error message for this error.
|
||||
/// </summary>
|
||||
public string Message { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The error code returned by the CMP.
|
||||
/// </summary>
|
||||
public int CmpCode { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The error message returned by the CMP.
|
||||
/// </summary>
|
||||
public string CmpMessage { get; private set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2e895983b04846af81b59189de0310c
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxCmpService.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// MaxEventExecutor.cs
|
||||
// Max Unity Plugin
|
||||
//
|
||||
// Created by Jonathan Liu on 1/22/2024.
|
||||
// Copyright © 2024 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace AppLovinMax.Internal
|
||||
{
|
||||
public class MaxEventExecutor : MonoBehaviour
|
||||
{
|
||||
private static MaxEventExecutor _instance;
|
||||
private static readonly List<MaxAction> AdEventsQueue = new List<MaxAction>();
|
||||
|
||||
private static volatile bool _adEventsQueueEmpty = true;
|
||||
|
||||
struct MaxAction
|
||||
{
|
||||
public readonly Action ActionToExecute;
|
||||
public readonly string EventName;
|
||||
|
||||
public MaxAction(Action actionToExecute, string nameOfEvent)
|
||||
{
|
||||
ActionToExecute = actionToExecute;
|
||||
EventName = nameOfEvent;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InitializeIfNeeded()
|
||||
{
|
||||
if (_instance != null) return;
|
||||
|
||||
var executor = new GameObject("MaxEventExecutor");
|
||||
executor.hideFlags = HideFlags.HideAndDontSave;
|
||||
DontDestroyOnLoad(executor);
|
||||
_instance = executor.AddComponent<MaxEventExecutor>();
|
||||
}
|
||||
|
||||
#region Public API
|
||||
|
||||
#if UNITY_EDITOR || !(UNITY_ANDROID || UNITY_IPHONE || UNITY_IOS)
|
||||
public static MaxEventExecutor Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static void ExecuteOnMainThread(Action action, string eventName)
|
||||
{
|
||||
lock (AdEventsQueue)
|
||||
{
|
||||
AdEventsQueue.Add(new MaxAction(action, eventName));
|
||||
_adEventsQueueEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeOnMainThread(UnityEvent unityEvent, string eventName)
|
||||
{
|
||||
ExecuteOnMainThread(() => unityEvent.Invoke(), eventName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_adEventsQueueEmpty) return;
|
||||
|
||||
var actionsToExecute = new List<MaxAction>();
|
||||
lock (AdEventsQueue)
|
||||
{
|
||||
actionsToExecute.AddRange(AdEventsQueue);
|
||||
AdEventsQueue.Clear();
|
||||
_adEventsQueueEmpty = true;
|
||||
}
|
||||
|
||||
foreach (var maxAction in actionsToExecute)
|
||||
{
|
||||
if (maxAction.ActionToExecute.Target != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
maxAction.ActionToExecute.Invoke();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MaxSdkLogger.UserError("Caught exception in publisher event: " + maxAction.EventName + ", exception: " + exception);
|
||||
MaxSdkLogger.LogException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
_instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4715dd62632564dc4810a4dc98243f4a
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxEventExecutor.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// EventSystemChecker.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Jonathan Liu on 10/23/2022.
|
||||
// Copyright © 2022 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace AppLovinMax.Scripts
|
||||
{
|
||||
/// <summary>
|
||||
/// A script to check and enable event system as needed for the AppLovin MAX ad prefabs.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(EventSystem))]
|
||||
public class MaxEventSystemChecker : MonoBehaviour
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
// Enable the EventSystem if there is no other EventSystem in the scene
|
||||
var eventSystem = GetComponent<EventSystem>();
|
||||
var currentSystem = EventSystem.current;
|
||||
if (currentSystem == null || currentSystem == eventSystem)
|
||||
{
|
||||
eventSystem.enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
eventSystem.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0acf281ba86b4929a6942ecd998395b
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxEventSystemChecker.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Class containing pre-defined constants to use with AppLovin event tracking APIs.
|
||||
*/
|
||||
public static class MaxEvents
|
||||
{
|
||||
/**
|
||||
* Nested class representing pre-defined AppLovin events to be fired with AppLovin event tracking APIs.
|
||||
*/
|
||||
public class AppLovin
|
||||
{
|
||||
public const string UserLoggedIn = "login";
|
||||
public const string UserCreatedAccount = "registration";
|
||||
public const string UserCompletedTutorial = "tutorial";
|
||||
public const string UserCompletedLevel = "level";
|
||||
public const string UserCompletedAchievement = "achievement";
|
||||
public const string UserSpentVirtualCurrency = "vcpurchase";
|
||||
public const string UserCompletedInAppPurchase = "iap";
|
||||
public const string UserSentInvitation = "invite";
|
||||
public const string UserSharedLink = "share";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67ec2f620c6b0405ba16ea2c032dc9a2
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxEvents.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "MaxSdk.Scripts",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4cfc1a18fa3a469b96d885db522f42e
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdk.Scripts.asmdef
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// AppLovin MAX Unity Plugin C# Wrapper
|
||||
//
|
||||
|
||||
public class MaxSdk :
|
||||
#if UNITY_EDITOR
|
||||
// Check for Unity Editor first since the editor also responds to the currently selected platform.
|
||||
MaxSdkUnityEditor
|
||||
#elif UNITY_ANDROID
|
||||
MaxSdkAndroid
|
||||
#elif UNITY_IPHONE || UNITY_IOS
|
||||
MaxSdkiOS
|
||||
#else
|
||||
MaxSdkUnityEditor
|
||||
#endif
|
||||
{
|
||||
private const string _version = "8.6.2";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current plugin version.
|
||||
/// </summary>
|
||||
public static string Version
|
||||
{
|
||||
get { return _version; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fc7aa576843c44e68c7ab14b475bb82
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdk.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,986 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using AppLovinMax.ThirdParty.MiniJson;
|
||||
|
||||
#if UNITY_ANDROID
|
||||
/// <summary>
|
||||
/// Android AppLovin MAX Unity Plugin implementation
|
||||
/// </summary>
|
||||
public class MaxSdkAndroid : MaxSdkBase
|
||||
{
|
||||
private static readonly AndroidJavaClass MaxUnityPluginClass =
|
||||
new AndroidJavaClass("com.applovin.mediation.unity.MaxUnityPlugin");
|
||||
|
||||
private static readonly BackgroundCallbackProxy BackgroundCallback = new BackgroundCallbackProxy();
|
||||
|
||||
static MaxSdkAndroid()
|
||||
{
|
||||
InitializeEventExecutor();
|
||||
|
||||
MaxUnityPluginClass.CallStatic("setBackgroundCallback", BackgroundCallback);
|
||||
}
|
||||
|
||||
#region Initialization
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the default instance of AppLovin SDK.
|
||||
///
|
||||
/// Please make sure that application's Android manifest or Info.plist includes the AppLovin SDK key.
|
||||
/// <param name="adUnitIds">
|
||||
/// OPTIONAL: Set the MAX ad unit ids to be used for this instance of the SDK. 3rd-party SDKs will be initialized with the credentials configured for these ad unit ids.
|
||||
/// This should only be used if you have different sets of ad unit ids / credentials for the same package name.</param>
|
||||
/// </summary>
|
||||
public static void InitializeSdk(string[] adUnitIds = null)
|
||||
{
|
||||
var serializedAdUnitIds = (adUnitIds != null) ? string.Join(",", adUnitIds) : "";
|
||||
MaxUnityPluginClass.CallStatic("initializeSdk", serializedAdUnitIds, GenerateMetaData());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the SDK has been initialized
|
||||
/// </summary>
|
||||
/// <returns>True if SDK has been initialized</returns>
|
||||
public static bool IsInitialized()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isInitialized");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Info
|
||||
|
||||
/// <summary>
|
||||
/// Set an identifier for the current user. This identifier will be tied to SDK events and our optional S2S postbacks.
|
||||
///
|
||||
/// If you're using reward validation, you can optionally set an identifier to be included with currency validation postbacks.
|
||||
/// For example, a username or email. We'll include this in the postback when we ping your currency endpoint from our server.
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="userId">The user identifier to be set. Must not be null.</param>
|
||||
public static void SetUserId(string userId)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setUserId", userId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the <see cref="MaxSegmentCollection"/>.
|
||||
/// </summary>
|
||||
/// <param name="segmentCollection"> The segment collection to be set. Must not be {@code null}</param>
|
||||
public static void SetSegmentCollection(MaxSegmentCollection segmentCollection)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setSegmentCollection", JsonUtility.ToJson(segmentCollection));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MAX
|
||||
|
||||
/// <summary>
|
||||
/// Returns the list of available mediation networks.
|
||||
///
|
||||
/// Please call this method after the SDK has initialized.
|
||||
/// </summary>
|
||||
public static List<MediatedNetworkInfo> GetAvailableMediatedNetworks()
|
||||
{
|
||||
var serializedNetworks = MaxUnityPluginClass.CallStatic<string>("getAvailableMediatedNetworks");
|
||||
return MaxSdkUtils.PropsStringsToList<MediatedNetworkInfo>(serializedNetworks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Present the mediation debugger UI.
|
||||
/// This debugger tool provides the status of your integration for each third-party ad network.
|
||||
///
|
||||
/// Please call this method after the SDK has initialized.
|
||||
/// </summary>
|
||||
public static void ShowMediationDebugger()
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("showMediationDebugger");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Present the creative debugger UI.
|
||||
/// This debugger tool provides information for recently displayed ads.
|
||||
///
|
||||
/// Please call this method after the SDK has initialized.
|
||||
/// </summary>
|
||||
public static void ShowCreativeDebugger()
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("showCreativeDebugger");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the arbitrary ad value for a given ad unit identifier with key. Returns null if no ad is loaded.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier for which to get the ad value for. Must not be null.</param>
|
||||
/// <param name="key">Ad value key. Must not be null.</param>
|
||||
/// <returns>Arbitrary ad value for a given key, or null if no ad is loaded.</returns>
|
||||
public static string GetAdValue(string adUnitIdentifier, string key)
|
||||
{
|
||||
var value = MaxUnityPluginClass.CallStatic<string>("getAdValue", adUnitIdentifier, key);
|
||||
|
||||
if (string.IsNullOrEmpty(value)) return null;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Privacy
|
||||
|
||||
/// <summary>
|
||||
/// Get the SDK configuration for this user.
|
||||
///
|
||||
/// Note: This method should be called only after SDK has been initialized.
|
||||
/// </summary>
|
||||
public static SdkConfiguration GetSdkConfiguration()
|
||||
{
|
||||
var sdkConfigurationStr = MaxUnityPluginClass.CallStatic<string>("getSdkConfiguration");
|
||||
var sdkConfigurationDict = Json.Deserialize(sdkConfigurationStr) as Dictionary<string, object>;
|
||||
return SdkConfiguration.Create(sdkConfigurationDict);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set whether or not user has provided consent for information sharing with AppLovin and other providers.
|
||||
/// </summary>
|
||||
/// <param name="hasUserConsent"><c>true</c> if the user has provided consent for information sharing with AppLovin. <c>false</c> by default.</param>
|
||||
public static void SetHasUserConsent(bool hasUserConsent)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setHasUserConsent", hasUserConsent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has provided consent for information sharing with AppLovin and other providers.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if user has provided consent for information sharing. <c>false</c> if the user declined to share information or the consent value has not been set. See <see cref="IsUserConsentSet">IsUserConsentSet</see>.</returns>
|
||||
public static bool HasUserConsent()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("hasUserConsent");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has set consent for information sharing.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if user has set a value of consent for information sharing.</returns>
|
||||
public static bool IsUserConsentSet()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isUserConsentSet");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set whether or not user has opted out of the sale of their personal information.
|
||||
/// </summary>
|
||||
/// <param name="doNotSell"><c>true</c> if the user has opted out of the sale of their personal information.</param>
|
||||
public static void SetDoNotSell(bool doNotSell)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setDoNotSell", doNotSell);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the user has opted out of the sale of their personal information.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if the user has opted out of the sale of their personal information. <c>false</c> if the user opted in to the sell of their personal information or the value has not been set. See <see cref="IsDoNotSellSet">IsDoNotSellSet</see>.</returns>
|
||||
public static bool IsDoNotSell()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isDoNotSell");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the user has set the option to sell their personal information.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if user has chosen an option to sell their personal information.</returns>
|
||||
public static bool IsDoNotSellSet()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isDoNotSellSet");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Banners
|
||||
|
||||
/// <summary>
|
||||
/// Create a new banner.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to create. Must not be null.</param>
|
||||
/// <param name="configuration">The configuration for the banner</param>
|
||||
public static void CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "create banner");
|
||||
if (configuration.UseCoordinates)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("createBanner", adUnitIdentifier, configuration.XCoordinate, configuration.YCoordinate, configuration.IsAdaptive);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("createBanner", adUnitIdentifier, configuration.Position.ToSnakeCaseString(), configuration.IsAdaptive);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a new banner ad.
|
||||
/// NOTE: The <see cref="CreateBanner()"/> method loads the first banner ad and initiates an automated banner refresh process.
|
||||
/// You only need to call this method if you pause banner refresh.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to load. Must not be null.</param>
|
||||
public static void LoadBanner(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "load banner");
|
||||
MaxUnityPluginClass.CallStatic("loadBanner", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the banner placement for an ad unit identifier to tie the future ad events to.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to set the placement for. Must not be null.</param>
|
||||
/// <param name="placement">Placement to set</param>
|
||||
public static void SetBannerPlacement(string adUnitIdentifier, string placement)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set banner placement");
|
||||
MaxUnityPluginClass.CallStatic("setBannerPlacement", adUnitIdentifier, placement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts or resumes auto-refreshing of the banner for the given ad unit identifier.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner for which to start auto-refresh. Must not be null.</param>
|
||||
public static void StartBannerAutoRefresh(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "start banner auto-refresh");
|
||||
MaxUnityPluginClass.CallStatic("startBannerAutoRefresh", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses auto-refreshing of the banner for the given ad unit identifier.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner for which to stop auto-refresh. Must not be null.</param>
|
||||
public static void StopBannerAutoRefresh(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "stop banner auto-refresh");
|
||||
MaxUnityPluginClass.CallStatic("stopBannerAutoRefresh", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of the banner to the new position provided.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">The ad unit identifier of the banner for which to update the position. Must not be null.</param>
|
||||
/// <param name="bannerPosition">A new position for the banner. Must not be null.</param>
|
||||
public static void UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "update banner position");
|
||||
MaxUnityPluginClass.CallStatic("updateBannerPosition", adUnitIdentifier, bannerPosition.ToSnakeCaseString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of the banner to the new coordinates provided.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">The ad unit identifier of the banner for which to update the position. Must not be null.</param>
|
||||
/// <param name="x">The X coordinate (horizontal position) of the banner relative to the top left corner of the screen.</param>
|
||||
/// <param name="y">The Y coordinate (vertical position) of the banner relative to the top left corner of the screen.</param>
|
||||
/// <seealso cref="GetBannerLayout">
|
||||
/// The banner is placed within the safe area of the screen. You can use this to get the absolute position of the banner on screen.
|
||||
/// </seealso>
|
||||
public static void UpdateBannerPosition(string adUnitIdentifier, float x, float y)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "update banner position");
|
||||
MaxUnityPluginClass.CallStatic("updateBannerPosition", adUnitIdentifier, x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the width of the banner in dp.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">The ad unit identifier of the banner for which to override the width for. Must not be null.</param>
|
||||
/// <param name="width">The desired width of the banner in dp</param>
|
||||
public static void SetBannerWidth(string adUnitIdentifier, float width)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set banner width");
|
||||
MaxUnityPluginClass.CallStatic("setBannerWidth", adUnitIdentifier, width);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show banner at a position determined by the 'CreateBanner' call.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to show. Must not be null.</param>
|
||||
public static void ShowBanner(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "show banner");
|
||||
MaxUnityPluginClass.CallStatic("showBanner", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove banner from the ad view and destroy it.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to destroy. Must not be null.</param>
|
||||
public static void DestroyBanner(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "destroy banner");
|
||||
MaxUnityPluginClass.CallStatic("destroyBanner", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide banner.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to hide. Must not be null.</param>
|
||||
public static void HideBanner(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "hide banner");
|
||||
MaxUnityPluginClass.CallStatic("hideBanner", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set non-transparent background color for banners to be fully functional.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to set background color for. Must not be null.</param>
|
||||
/// <param name="color">A background color to set for the ad. Must not be null.</param>
|
||||
public static void SetBannerBackgroundColor(string adUnitIdentifier, Color color)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set background color");
|
||||
MaxUnityPluginClass.CallStatic("setBannerBackgroundColor", adUnitIdentifier, MaxSdkUtils.ParseColor(color));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an extra parameter for the banner ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to set the extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter.</param>
|
||||
public static void SetBannerExtraParameter(string adUnitIdentifier, string key, string value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set banner extra parameter");
|
||||
MaxUnityPluginClass.CallStatic("setBannerExtraParameter", adUnitIdentifier, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a local extra parameter for the banner ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner to set the local extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter. Accepts the following types: <see cref="AndroidJavaObject"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
|
||||
public static void SetBannerLocalExtraParameter(string adUnitIdentifier, string key, object value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set banner local extra parameter");
|
||||
|
||||
if (value == null || value is AndroidJavaObject)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setBannerLocalExtraParameter", adUnitIdentifier, key, (AndroidJavaObject) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setBannerLocalExtraParameterJson", adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The custom data to tie the showing banner ad to, for ILRD and rewarded postbacks via the <c>{CUSTOM_DATA}</c> macro. Maximum size is 8KB.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Banner ad unit identifier of the banner to set the custom data for. Must not be null.</param>
|
||||
/// <param name="customData">The custom data to be set.</param>
|
||||
public static void SetBannerCustomData(string adUnitIdentifier, string customData)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set banner custom data");
|
||||
MaxUnityPluginClass.CallStatic("setBannerCustomData", adUnitIdentifier, customData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The banner position on the screen. When setting the banner position via <see cref="CreateBanner(string, float, float)"/> or <see cref="UpdateBannerPosition(string, float, float)"/>,
|
||||
/// the banner is placed within the safe area of the screen. This returns the absolute position of the banner on screen.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the banner for which to get the position on screen. Must not be null.</param>
|
||||
/// <returns>A <see cref="Rect"/> representing the banner position on screen.</returns>
|
||||
public static Rect GetBannerLayout(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "get banner layout");
|
||||
var positionRect = MaxUnityPluginClass.CallStatic<string>("getBannerLayout", adUnitIdentifier);
|
||||
return GetRectFromString(positionRect);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MRECs
|
||||
|
||||
/// <summary>
|
||||
/// Create a new MREC.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to create. Must not be null.</param>
|
||||
/// <param name="configuration">The configuration for the MREC.</param>
|
||||
public static void CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "create MREC");
|
||||
if (configuration.UseCoordinates)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("createMRec", adUnitIdentifier, configuration.XCoordinate, configuration.YCoordinate);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("createMRec", adUnitIdentifier, configuration.Position.ToSnakeCaseString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a new MREC ad.
|
||||
/// NOTE: The <see cref="CreateMRec()"/> method loads the first MREC ad and initiates an automated MREC refresh process.
|
||||
/// You only need to call this method if you pause MREC refresh.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to load. Must not be null.</param>
|
||||
public static void LoadMRec(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "load MREC");
|
||||
MaxUnityPluginClass.CallStatic("loadMRec", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the MREC placement for an ad unit identifier to tie the future ad events to.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to set the placement for. Must not be null.</param>
|
||||
/// <param name="placement">Placement to set</param>
|
||||
public static void SetMRecPlacement(string adUnitIdentifier, string placement)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set MREC placement");
|
||||
MaxUnityPluginClass.CallStatic("setMRecPlacement", adUnitIdentifier, placement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts or resumes auto-refreshing of the MREC for the given ad unit identifier.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC for which to start auto-refresh. Must not be null.</param>
|
||||
public static void StartMRecAutoRefresh(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "start MREC auto-refresh");
|
||||
MaxUnityPluginClass.CallStatic("startMRecAutoRefresh", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses auto-refreshing of the MREC for the given ad unit identifier.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC for which to stop auto-refresh. Must not be null.</param>
|
||||
public static void StopMRecAutoRefresh(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "stop MREC auto-refresh");
|
||||
MaxUnityPluginClass.CallStatic("stopMRecAutoRefresh", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of the MREC to the new position provided.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">The ad unit identifier of the MREC for which to update the position. Must not be null.</param>
|
||||
/// <param name="mrecPosition">A new position for the MREC. Must not be null.</param>
|
||||
public static void UpdateMRecPosition(string adUnitIdentifier, AdViewPosition mrecPosition)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "update MREC position");
|
||||
MaxUnityPluginClass.CallStatic("updateMRecPosition", adUnitIdentifier, mrecPosition.ToSnakeCaseString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the position of the MREC to the new coordinates provided.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">The ad unit identifier of the MREC for which to update the position. Must not be null.</param>
|
||||
/// <param name="x">The X coordinate (horizontal position) of the MREC relative to the top left corner of the screen.</param>
|
||||
/// <param name="y">The Y coordinate (vertical position) of the MREC relative to the top left corner of the screen.</param>
|
||||
/// <seealso cref="GetMRecLayout">
|
||||
/// The MREC is placed within the safe area of the screen. You can use this to get the absolute position Rect of the MREC on screen.
|
||||
/// </seealso>
|
||||
public static void UpdateMRecPosition(string adUnitIdentifier, float x, float y)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "update MREC position");
|
||||
MaxUnityPluginClass.CallStatic("updateMRecPosition", adUnitIdentifier, x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show MREC at a position determined by the 'CreateMRec' call.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to show. Must not be null.</param>
|
||||
public static void ShowMRec(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "show MREC");
|
||||
MaxUnityPluginClass.CallStatic("showMRec", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove MREC from the ad view and destroy it.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to destroy. Must not be null.</param>
|
||||
public static void DestroyMRec(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "destroy MREC");
|
||||
MaxUnityPluginClass.CallStatic("destroyMRec", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hide MREC.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to hide. Must not be null.</param>
|
||||
public static void HideMRec(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "hide MREC");
|
||||
MaxUnityPluginClass.CallStatic("hideMRec", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an extra parameter for the MREC ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to set the extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter.</param>
|
||||
public static void SetMRecExtraParameter(string adUnitIdentifier, string key, string value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set MREC extra parameter");
|
||||
MaxUnityPluginClass.CallStatic("setMRecExtraParameter", adUnitIdentifier, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a local extra parameter for the MREC ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC to set the local extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter. Accepts the following types: <see cref="AndroidJavaObject"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
|
||||
public static void SetMRecLocalExtraParameter(string adUnitIdentifier, string key, object value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set MREC local extra parameter");
|
||||
|
||||
if (value == null || value is AndroidJavaObject)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setMRecLocalExtraParameter", adUnitIdentifier, key, (AndroidJavaObject) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setMRecLocalExtraParameterJson", adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The custom data to tie the showing MREC ad to, for ILRD and rewarded postbacks via the <c>{CUSTOM_DATA}</c> macro. Maximum size is 8KB.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">MREC Ad unit identifier of the banner to set the custom data for. Must not be null.</param>
|
||||
/// <param name="customData">The custom data to be set.</param>
|
||||
public static void SetMRecCustomData(string adUnitIdentifier, string customData)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set MREC custom data");
|
||||
MaxUnityPluginClass.CallStatic("setMRecCustomData", adUnitIdentifier, customData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MREC position on the screen. When setting the banner position via <see cref="CreateMRec(string, float, float)"/> or <see cref="UpdateMRecPosition(string, float, float)"/>,
|
||||
/// the banner is placed within the safe area of the screen. This returns the absolute position of the MREC on screen.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the MREC for which to get the position on screen. Must not be null.</param>
|
||||
/// <returns>A <see cref="Rect"/> representing the banner position on screen.</returns>
|
||||
public static Rect GetMRecLayout(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "get MREC layout");
|
||||
var positionRect = MaxUnityPluginClass.CallStatic<string>("getMRecLayout", adUnitIdentifier);
|
||||
return GetRectFromString(positionRect);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interstitials
|
||||
|
||||
/// <summary>
|
||||
/// Start loading an interstitial.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the interstitial to load. Must not be null.</param>
|
||||
public static void LoadInterstitial(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "load interstitial");
|
||||
MaxUnityPluginClass.CallStatic("loadInterstitial", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if interstitial ad is loaded and ready to be displayed.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the interstitial to load. Must not be null.</param>
|
||||
/// <returns>True if the ad is ready to be displayed</returns>
|
||||
public static bool IsInterstitialReady(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "check interstitial loaded");
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isInterstitialReady", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Present loaded interstitial for a given placement to tie ad events to. Note: if the interstitial is not ready to be displayed nothing will happen.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the interstitial to load. Must not be null.</param>
|
||||
/// <param name="placement">The placement to tie the showing ad's events to</param>
|
||||
/// <param name="customData">The custom data to tie the showing ad's events to. Maximum size is 8KB.</param>
|
||||
public static void ShowInterstitial(string adUnitIdentifier, string placement = null, string customData = null)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "show interstitial");
|
||||
|
||||
if (IsInterstitialReady(adUnitIdentifier))
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("showInterstitial", adUnitIdentifier, placement, customData);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Not showing MAX Ads interstitial: ad not ready");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an extra parameter for the ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the interstitial to set the extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter.</param>
|
||||
public static void SetInterstitialExtraParameter(string adUnitIdentifier, string key, string value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set interstitial extra parameter");
|
||||
MaxUnityPluginClass.CallStatic("setInterstitialExtraParameter", adUnitIdentifier, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a local extra parameter for the ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the interstitial to set the local extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter. Accepts the following types: <see cref="AndroidJavaObject"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
|
||||
public static void SetInterstitialLocalExtraParameter(string adUnitIdentifier, string key, object value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set interstitial local extra parameter");
|
||||
|
||||
if (value == null || value is AndroidJavaObject)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setInterstitialLocalExtraParameter", adUnitIdentifier, key, (AndroidJavaObject) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setInterstitialLocalExtraParameterJson", adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region App Open
|
||||
|
||||
/// <summary>
|
||||
/// Start loading an app open ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the app open ad to load. Must not be null.</param>
|
||||
public static void LoadAppOpenAd(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "load app open ad");
|
||||
MaxUnityPluginClass.CallStatic("loadAppOpenAd", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if app open ad ad is loaded and ready to be displayed.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the app open ad to load. Must not be null.</param>
|
||||
/// <returns>True if the ad is ready to be displayed</returns>
|
||||
public static bool IsAppOpenAdReady(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "check app open ad loaded");
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isAppOpenAdReady", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Present loaded app open ad for a given placement to tie ad events to. Note: if the app open ad is not ready to be displayed nothing will happen.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the app open ad to load. Must not be null.</param>
|
||||
/// <param name="placement">The placement to tie the showing ad's events to</param>
|
||||
/// <param name="customData">The custom data to tie the showing ad's events to. Maximum size is 8KB.</param>
|
||||
public static void ShowAppOpenAd(string adUnitIdentifier, string placement = null, string customData = null)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "show app open ad");
|
||||
|
||||
if (IsAppOpenAdReady(adUnitIdentifier))
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("showAppOpenAd", adUnitIdentifier, placement, customData);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Not showing MAX Ads app open ad: ad not ready");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an extra parameter for the ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the app open ad to set the extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter.</param>
|
||||
public static void SetAppOpenAdExtraParameter(string adUnitIdentifier, string key, string value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set app open ad extra parameter");
|
||||
MaxUnityPluginClass.CallStatic("setAppOpenAdExtraParameter", adUnitIdentifier, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a local extra parameter for the ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the app open ad to set the local extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter. Accepts the following types: <see cref="AndroidJavaObject"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
|
||||
public static void SetAppOpenAdLocalExtraParameter(string adUnitIdentifier, string key, object value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set app open ad local extra parameter");
|
||||
|
||||
if (value == null || value is AndroidJavaObject)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setAppOpenAdLocalExtraParameter", adUnitIdentifier, key, (AndroidJavaObject) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setAppOpenAdLocalExtraParameterJson", adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rewarded
|
||||
|
||||
/// <summary>
|
||||
/// Start loading an rewarded ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded ad to load. Must not be null.</param>
|
||||
public static void LoadRewardedAd(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "load rewarded ad");
|
||||
MaxUnityPluginClass.CallStatic("loadRewardedAd", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if rewarded ad ad is loaded and ready to be displayed.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded ad to load. Must not be null.</param>
|
||||
/// <returns>True if the ad is ready to be displayed</returns>
|
||||
public static bool IsRewardedAdReady(string adUnitIdentifier)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "check rewarded ad loaded");
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isRewardedAdReady", adUnitIdentifier);
|
||||
}
|
||||
|
||||
/// <summary> ready to be
|
||||
/// Present loaded rewarded ad for a given placement to tie ad events to. Note: if the rewarded ad is not ready to be displayed nothing will happen.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the interstitial to load. Must not be null.</param>
|
||||
/// <param name="placement">The placement to tie the showing ad's events to</param>
|
||||
/// <param name="customData">The custom data to tie the showing ad's events to. Maximum size is 8KB.</param>
|
||||
public static void ShowRewardedAd(string adUnitIdentifier, string placement = null, string customData = null)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "show rewarded ad");
|
||||
|
||||
if (IsRewardedAdReady(adUnitIdentifier))
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("showRewardedAd", adUnitIdentifier, placement, customData);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxSdkLogger.UserWarning("Not showing MAX Ads rewarded ad: ad not ready");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an extra parameter for the ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded to set the extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter.</param>
|
||||
public static void SetRewardedAdExtraParameter(string adUnitIdentifier, string key, string value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded ad extra parameter");
|
||||
MaxUnityPluginClass.CallStatic("setRewardedAdExtraParameter", adUnitIdentifier, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a local extra parameter for the ad.
|
||||
/// </summary>
|
||||
/// <param name="adUnitIdentifier">Ad unit identifier of the rewarded to set the local extra parameter for. Must not be null.</param>
|
||||
/// <param name="key">The key for the local extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter. Accepts the following types: <see cref="AndroidJavaObject"/>, <c>null</c>, <c>IList</c>, <c>IDictionary</c>, <c>string</c>, primitive types</param>
|
||||
public static void SetRewardedAdLocalExtraParameter(string adUnitIdentifier, string key, object value)
|
||||
{
|
||||
ValidateAdUnitIdentifier(adUnitIdentifier, "set rewarded ad local extra parameter");
|
||||
|
||||
if (value == null || value is AndroidJavaObject)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setRewardedAdLocalExtraParameter", adUnitIdentifier, key, (AndroidJavaObject) value);
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setRewardedAdLocalExtraParameterJson", adUnitIdentifier, key, SerializeLocalExtraParameterValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Tracking
|
||||
|
||||
/// <summary>
|
||||
/// Track an event using AppLovin.
|
||||
/// </summary>
|
||||
/// <param name="name">An event from the list of pre-defined events may be found in MaxEvents.cs as part of the AppLovin SDK framework. Must not be null.</param>
|
||||
/// <param name="parameters">A dictionary containing key-value pairs further describing this event.</param>
|
||||
public static void TrackEvent(string name, IDictionary<string, string> parameters = null)
|
||||
{
|
||||
// Convert null to "{}" to avoid Unity sending the literal "null" to Android.
|
||||
var jsonString = ( parameters == null ) ? EmptyJson : Json.Serialize(parameters);
|
||||
MaxUnityPluginClass.CallStatic("trackEvent", name, jsonString);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Settings
|
||||
|
||||
/// <summary>
|
||||
/// Set whether to begin video ads in a muted state or not.
|
||||
///
|
||||
/// Please call this method after the SDK has initialized.
|
||||
/// </summary>
|
||||
/// <param name="muted"><c>true</c> if video ads should being in muted state.</param>
|
||||
public static void SetMuted(bool muted)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setMuted", muted);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether video ads begin in a muted state or not. Defaults to <c>false</c>.
|
||||
///
|
||||
/// Note: Returns <c>false</c> if the SDK is not initialized.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if video ads begin in muted state.</returns>
|
||||
public static bool IsMuted()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isMuted");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle verbose logging of AppLovin SDK. If enabled AppLovin messages will appear in standard application log accessible via logcat. All log messages will have "AppLovinSdk" tag.
|
||||
/// </summary>
|
||||
/// <param name="enabled"><c>true</c> if verbose logging should be enabled.</param>
|
||||
public static void SetVerboseLogging(bool enabled)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setVerboseLogging", enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not verbose logging is enabled.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if verbose logging is enabled.</returns>
|
||||
public static bool IsVerboseLoggingEnabled()
|
||||
{
|
||||
return MaxUnityPluginClass.CallStatic<bool>("isVerboseLoggingEnabled");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the creative debugger will be displayed on fullscreen ads after flipping the device screen down twice. Defaults to true.
|
||||
/// </summary>
|
||||
/// <param name="enabled"><c>true</c> if the creative debugger should be enabled.</param>
|
||||
public static void SetCreativeDebuggerEnabled(bool enabled)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setCreativeDebuggerEnabled", enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enable devices to receive test ads, by passing in the advertising identifier (IDFA/GAID) of each test device.
|
||||
/// Refer to AppLovin logs for the IDFA/GAID of your current device.
|
||||
/// </summary>
|
||||
/// <param name="advertisingIdentifiers">String list of advertising identifiers from devices to receive test ads.</param>
|
||||
public static void SetTestDeviceAdvertisingIdentifiers(string[] advertisingIdentifiers)
|
||||
{
|
||||
if (IsInitialized())
|
||||
{
|
||||
MaxSdkLogger.UserError("Test Device Advertising Identifiers must be set before SDK initialization.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrap the string array in an object array, so the compiler does not split into multiple strings.
|
||||
object[] arguments = {advertisingIdentifiers};
|
||||
MaxUnityPluginClass.CallStatic("setTestDeviceAdvertisingIds", arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the native AppLovin SDKs listen to exceptions. Defaults to <c>true</c>.
|
||||
/// </summary>
|
||||
/// <param name="enabled"><c>true</c> if the native AppLovin SDKs should not listen to exceptions.</param>
|
||||
public static void SetExceptionHandlerEnabled(bool enabled)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setExceptionHandlerEnabled", enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set an extra parameter to pass to the AppLovin server.
|
||||
/// </summary>
|
||||
/// <param name="key">The key for the extra parameter. Must not be null.</param>
|
||||
/// <param name="value">The value for the extra parameter. May be null.</param>
|
||||
public static void SetExtraParameter(string key, string value)
|
||||
{
|
||||
HandleExtraParameter(key, value);
|
||||
|
||||
MaxUnityPluginClass.CallStatic("setExtraParameter", key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the native insets in pixels for the safe area.
|
||||
/// These insets are used to position ads within the safe area of the screen.
|
||||
/// </summary>
|
||||
public static SafeAreaInsets GetSafeAreaInsets()
|
||||
{
|
||||
// Use an int array instead of json serialization for performance
|
||||
var insets = MaxUnityPluginClass.CallStatic<int[]>("getSafeAreaInsets");
|
||||
|
||||
// Convert from points to pixels
|
||||
var screenDensity = MaxSdkUtils.GetScreenDensity();
|
||||
for (var i = 0; i < insets.Length; i++)
|
||||
{
|
||||
insets[i] *= (int) screenDensity;
|
||||
}
|
||||
|
||||
return new SafeAreaInsets(insets);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
|
||||
public static void CreateBanner(string adUnitIdentifier, BannerPosition bannerPosition)
|
||||
{
|
||||
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
|
||||
CreateBanner(adUnitIdentifier, new AdViewConfiguration((AdViewPosition) bannerPosition));
|
||||
}
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateBanner(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
|
||||
public static void CreateBanner(string adUnitIdentifier, float x, float y)
|
||||
{
|
||||
CreateBanner(adUnitIdentifier, new AdViewConfiguration(x, y));
|
||||
}
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please use UpdateBannerPosition(string adUnitIdentifier, AdViewPosition bannerPosition) instead.")]
|
||||
public static void UpdateBannerPosition(string adUnitIdentifier, BannerPosition bannerPosition)
|
||||
{
|
||||
// AdViewPosition and BannerPosition share identical enum values, so casting is safe
|
||||
UpdateBannerPosition(adUnitIdentifier, (AdViewPosition) bannerPosition);
|
||||
}
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
|
||||
public static void CreateMRec(string adUnitIdentifier, AdViewPosition mrecPosition)
|
||||
{
|
||||
CreateMRec(adUnitIdentifier, new AdViewConfiguration(mrecPosition));
|
||||
}
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please use CreateMRec(string adUnitIdentifier, AdViewConfiguration configuration) instead.")]
|
||||
public static void CreateMRec(string adUnitIdentifier, float x, float y)
|
||||
{
|
||||
CreateMRec(adUnitIdentifier, new AdViewConfiguration(x, y));
|
||||
}
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.")]
|
||||
public static void SetSdkKey(string sdkKey)
|
||||
{
|
||||
MaxUnityPluginClass.CallStatic("setSdkKey", sdkKey);
|
||||
MaxSdkLogger.UserWarning("MaxSdk.SetSdkKey() has been deprecated and will be removed in a future release. Please set your SDK key in the AppLovin Integration Manager.");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal class BackgroundCallbackProxy : AndroidJavaProxy
|
||||
{
|
||||
public BackgroundCallbackProxy() : base("com.applovin.mediation.unity.MaxUnityAdManager$BackgroundCallback") { }
|
||||
|
||||
public void onEvent(string propsStr)
|
||||
{
|
||||
HandleBackgroundCallback(propsStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10335fad6bfef47b8819a411aa591dc8
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkAndroid.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,835 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AppLovinMax.ThirdParty.MiniJson;
|
||||
using AppLovinMax.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
#if UNITY_IOS && !UNITY_EDITOR
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
public abstract class MaxSdkBase
|
||||
{
|
||||
protected const string EmptyJson = "{}";
|
||||
|
||||
/// <summary>
|
||||
/// This enum represents the user's geography used to determine the type of consent flow shown to the user.
|
||||
/// </summary>
|
||||
public enum ConsentFlowUserGeography
|
||||
{
|
||||
/// <summary>
|
||||
/// User's geography is unknown.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// The user is in GDPR region.
|
||||
/// </summary>
|
||||
Gdpr,
|
||||
|
||||
/// <summary>
|
||||
/// The user is in a non-GDPR region.
|
||||
/// </summary>
|
||||
Other
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR || UNITY_IPHONE || UNITY_IOS
|
||||
/// <summary>
|
||||
/// App tracking status values. Primarily used in conjunction with iOS14's AppTrackingTransparency.framework.
|
||||
/// </summary>
|
||||
public enum AppTrackingStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Device is on < iOS14, AppTrackingTransparency.framework is not available.
|
||||
/// </summary>
|
||||
Unavailable,
|
||||
|
||||
/// <summary>
|
||||
/// The value returned if a user has not yet received an authorization request to authorize access to app-related data that can be used for tracking the user or the device.
|
||||
/// </summary>
|
||||
NotDetermined,
|
||||
|
||||
/// <summary>
|
||||
/// The value returned if authorization to access app-related data that can be used for tracking the user or the device is restricted.
|
||||
/// </summary>
|
||||
Restricted,
|
||||
|
||||
/// <summary>
|
||||
/// The value returned if the user denies authorization to access app-related data that can be used for tracking the user or the device.
|
||||
/// </summary>
|
||||
Denied,
|
||||
|
||||
/// <summary>
|
||||
/// The value returned if the user authorizes access to app-related data that can be used for tracking the user or the device.
|
||||
/// </summary>
|
||||
Authorized,
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// An enum describing the adapter's initialization status.
|
||||
/// </summary>
|
||||
public enum InitializationStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The adapter is not initialized. Note: networks need to be enabled for an ad unit id to be initialized.
|
||||
/// </summary>
|
||||
NotInitialized = -4,
|
||||
|
||||
/// <summary>
|
||||
/// The 3rd-party SDK does not have an initialization callback with status.
|
||||
/// </summary>
|
||||
DoesNotApply = -3,
|
||||
|
||||
/// <summary>
|
||||
/// The 3rd-party SDK is currently initializing.
|
||||
/// </summary>
|
||||
Initializing = -2,
|
||||
|
||||
/// <summary>
|
||||
/// The 3rd-party SDK explicitly initialized, but without a status.
|
||||
/// </summary>
|
||||
InitializedUnknown = -1,
|
||||
|
||||
/// <summary>
|
||||
/// The 3rd-party SDK initialization failed.
|
||||
/// </summary>
|
||||
InitializedFailure = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The 3rd-party SDK initialization was successful.
|
||||
/// </summary>
|
||||
InitializedSuccess = 1
|
||||
}
|
||||
|
||||
public enum AdViewPosition
|
||||
{
|
||||
TopLeft,
|
||||
TopCenter,
|
||||
TopRight,
|
||||
Centered,
|
||||
CenterLeft,
|
||||
CenterRight,
|
||||
BottomLeft,
|
||||
BottomCenter,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
public class AdViewConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The position of the ad.
|
||||
/// </summary>
|
||||
public AdViewPosition Position { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The horizontal (X) position of the banner, relative to the top-left corner of the screen's safe area.
|
||||
/// </summary>
|
||||
public float XCoordinate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The vertical (Y) position of the banner, relative to the top-left corner of the screen's safe area.
|
||||
/// </summary>
|
||||
public float YCoordinate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to use adaptive banners. Has no effect on MREC ads.
|
||||
/// </summary>
|
||||
public bool IsAdaptive { get; set; }
|
||||
|
||||
internal bool UseCoordinates { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AdViewConfiguration with the given AdViewPosition.
|
||||
/// </summary>
|
||||
/// <param name="position">The position of the ad. Must not be null.</param>
|
||||
public AdViewConfiguration(AdViewPosition position)
|
||||
{
|
||||
Position = position;
|
||||
IsAdaptive = true;
|
||||
UseCoordinates = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an AdViewConfiguration with the given x and y coordinates.
|
||||
/// </summary>
|
||||
/// <param name="x">The horizontal (X) position of the banner, relative to the top-left corner of the screen's safe area.</param>
|
||||
/// <param name="y">The vertical (Y) position of the banner, relative to the top-left corner of the screen's safe area.</param>
|
||||
public AdViewConfiguration(float x, float y)
|
||||
{
|
||||
XCoordinate = x;
|
||||
YCoordinate = y;
|
||||
IsAdaptive = true;
|
||||
UseCoordinates = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class SdkConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not the SDK has been initialized successfully.
|
||||
/// </summary>
|
||||
public bool IsSuccessfullyInitialized { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the country code for this user.
|
||||
/// </summary>
|
||||
public string CountryCode { get; private set; }
|
||||
|
||||
#if UNITY_EDITOR || UNITY_IPHONE || UNITY_IOS
|
||||
/// <summary>
|
||||
/// App tracking status values. Primarily used in conjunction with iOS14's AppTrackingTransparency.framework.
|
||||
/// </summary>
|
||||
public AppTrackingStatus AppTrackingStatus { get; private set; }
|
||||
#endif
|
||||
|
||||
public bool IsTestModeEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the user's geography used to determine the type of consent flow shown to the user.
|
||||
/// If no such determination could be made, <see cref="MaxSdkBase.ConsentFlowUserGeography.Unknown"/> will be returned.
|
||||
/// </summary>
|
||||
public ConsentFlowUserGeography ConsentFlowUserGeography { get; private set; }
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release.")]
|
||||
public ConsentDialogState ConsentDialogState { get; private set; }
|
||||
|
||||
#if UNITY_EDITOR || !(UNITY_ANDROID || UNITY_IPHONE || UNITY_IOS)
|
||||
public static SdkConfiguration CreateEmpty()
|
||||
{
|
||||
var sdkConfiguration = new SdkConfiguration();
|
||||
sdkConfiguration.IsSuccessfullyInitialized = true;
|
||||
#pragma warning disable 0618
|
||||
sdkConfiguration.ConsentDialogState = ConsentDialogState.Unknown;
|
||||
#pragma warning restore 0618
|
||||
#if UNITY_EDITOR
|
||||
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.Authorized;
|
||||
#endif
|
||||
sdkConfiguration.CountryCode = TryGetCountryCode();
|
||||
sdkConfiguration.IsTestModeEnabled = false;
|
||||
|
||||
return sdkConfiguration;
|
||||
}
|
||||
|
||||
private static string TryGetCountryCode()
|
||||
{
|
||||
try
|
||||
{
|
||||
return RegionInfo.CurrentRegion.TwoLetterISORegionName;
|
||||
}
|
||||
#pragma warning disable 0168
|
||||
catch (Exception ignored)
|
||||
#pragma warning restore 0168
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
|
||||
return "US";
|
||||
}
|
||||
#endif
|
||||
|
||||
public static SdkConfiguration Create(IDictionary<string, object> eventProps)
|
||||
{
|
||||
var sdkConfiguration = new SdkConfiguration();
|
||||
|
||||
sdkConfiguration.IsSuccessfullyInitialized = MaxSdkUtils.GetBoolFromDictionary(eventProps, "isSuccessfullyInitialized");
|
||||
sdkConfiguration.CountryCode = MaxSdkUtils.GetStringFromDictionary(eventProps, "countryCode", "");
|
||||
sdkConfiguration.IsTestModeEnabled = MaxSdkUtils.GetBoolFromDictionary(eventProps, "isTestModeEnabled");
|
||||
|
||||
var consentFlowUserGeographyStr = MaxSdkUtils.GetStringFromDictionary(eventProps, "consentFlowUserGeography", "");
|
||||
if ("1".Equals(consentFlowUserGeographyStr))
|
||||
{
|
||||
sdkConfiguration.ConsentFlowUserGeography = ConsentFlowUserGeography.Gdpr;
|
||||
}
|
||||
else if ("2".Equals(consentFlowUserGeographyStr))
|
||||
{
|
||||
sdkConfiguration.ConsentFlowUserGeography = ConsentFlowUserGeography.Other;
|
||||
}
|
||||
else
|
||||
{
|
||||
sdkConfiguration.ConsentFlowUserGeography = ConsentFlowUserGeography.Unknown;
|
||||
}
|
||||
|
||||
#pragma warning disable 0618
|
||||
var consentDialogStateStr = MaxSdkUtils.GetStringFromDictionary(eventProps, "consentDialogState", "");
|
||||
if ("1".Equals(consentDialogStateStr))
|
||||
{
|
||||
sdkConfiguration.ConsentDialogState = ConsentDialogState.Applies;
|
||||
}
|
||||
else if ("2".Equals(consentDialogStateStr))
|
||||
{
|
||||
sdkConfiguration.ConsentDialogState = ConsentDialogState.DoesNotApply;
|
||||
}
|
||||
else
|
||||
{
|
||||
sdkConfiguration.ConsentDialogState = ConsentDialogState.Unknown;
|
||||
}
|
||||
#pragma warning restore 0618
|
||||
|
||||
#if UNITY_IPHONE || UNITY_IOS
|
||||
var appTrackingStatusStr = MaxSdkUtils.GetStringFromDictionary(eventProps, "appTrackingStatus", "-1");
|
||||
if ("-1".Equals(appTrackingStatusStr))
|
||||
{
|
||||
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.Unavailable;
|
||||
}
|
||||
else if ("0".Equals(appTrackingStatusStr))
|
||||
{
|
||||
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.NotDetermined;
|
||||
}
|
||||
else if ("1".Equals(appTrackingStatusStr))
|
||||
{
|
||||
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.Restricted;
|
||||
}
|
||||
else if ("2".Equals(appTrackingStatusStr))
|
||||
{
|
||||
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.Denied;
|
||||
}
|
||||
else // "3" is authorized
|
||||
{
|
||||
sdkConfiguration.AppTrackingStatus = AppTrackingStatus.Authorized;
|
||||
}
|
||||
#endif
|
||||
|
||||
return sdkConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
public struct Reward
|
||||
{
|
||||
public string Label;
|
||||
public int Amount;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "Reward: " + Amount + " " + Label;
|
||||
}
|
||||
|
||||
public bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Label) && Amount > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum contains various error codes that the SDK can return when a MAX ad fails to load or display.
|
||||
*/
|
||||
public enum ErrorCode
|
||||
{
|
||||
/// <summary>
|
||||
/// This error code represents an error that could not be categorized into one of the other defined errors. See the message field in the error object for more details.
|
||||
/// </summary>
|
||||
Unspecified = -1,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that MAX returned no eligible ads from any mediated networks for this app/device.
|
||||
/// </summary>
|
||||
NoFill = 204,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that MAX returned eligible ads from mediated networks, but all ads failed to load. See the adLoadFailureInfo field in the error object for more details.
|
||||
/// </summary>
|
||||
AdLoadFailed = -5001,
|
||||
|
||||
/// <summary>
|
||||
/// This error code represents an error that was encountered when showing an ad.
|
||||
/// </summary>
|
||||
AdDisplayFailed = -4205,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that the ad request failed due to a generic network error. See the message field in the error object for more details.
|
||||
/// </summary>
|
||||
NetworkError = -1000,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that the ad request timed out due to a slow internet connection.
|
||||
/// </summary>
|
||||
NetworkTimeout = -1001,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that the ad request failed because the device is not connected to the internet.
|
||||
/// </summary>
|
||||
NoNetwork = -1009,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that you attempted to show a fullscreen ad while another fullscreen ad is still showing.
|
||||
/// </summary>
|
||||
FullscreenAdAlreadyShowing = -23,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates you are attempting to show a fullscreen ad before the one has been loaded.
|
||||
/// </summary>
|
||||
FullscreenAdNotReady = -24,
|
||||
|
||||
#if UNITY_IOS || UNITY_IPHONE
|
||||
/// <summary>
|
||||
/// This error code indicates you attempted to present a fullscreen ad from an invalid view controller.
|
||||
/// </summary>
|
||||
FullscreenAdInvalidViewController = -25,
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates you are attempting to load a fullscreen ad while another fullscreen ad is already loading.
|
||||
/// </summary>
|
||||
FullscreenAdAlreadyLoading = -26,
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates you are attempting to load a fullscreen ad while another fullscreen ad is still showing.
|
||||
/// </summary>
|
||||
FullscreenAdLoadWhileShowing = -27,
|
||||
|
||||
#if UNITY_ANDROID
|
||||
/// <summary>
|
||||
/// This error code indicates that the SDK failed to display an ad because the user has the "Don't Keep Activities" developer setting enabled.
|
||||
/// </summary>
|
||||
DontKeepActivitiesEnabled = -5602,
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// This error code indicates that the SDK failed to load an ad because the publisher provided an invalid ad unit identifier.
|
||||
/// Possible reasons for an invalid ad unit identifier:
|
||||
/// 1. Ad unit identifier is malformed or does not exist
|
||||
/// 2. Ad unit is disabled
|
||||
/// 3. Ad unit is not associated with the current app's package name
|
||||
/// 4. Ad unit was created within the last 30-60 minutes
|
||||
/// </summary>
|
||||
InvalidAdUnitId = -5603
|
||||
}
|
||||
|
||||
/**
|
||||
* This enum contains possible states of an ad in the waterfall the adapter response info could represent.
|
||||
*/
|
||||
public enum MaxAdLoadState
|
||||
{
|
||||
/// <summary>
|
||||
/// The AppLovin Max SDK did not attempt to load an ad from this network in the waterfall because an ad higher
|
||||
/// in the waterfall loaded successfully.
|
||||
/// </summary>
|
||||
AdLoadNotAttempted,
|
||||
|
||||
/// <summary>
|
||||
/// An ad successfully loaded from this network.
|
||||
/// </summary>
|
||||
AdLoaded,
|
||||
|
||||
/// <summary>
|
||||
/// An ad failed to load from this network.
|
||||
/// </summary>
|
||||
FailedToLoad
|
||||
}
|
||||
|
||||
public class AdInfo
|
||||
{
|
||||
public string AdUnitIdentifier { get; private set; }
|
||||
public string AdFormat { get; private set; }
|
||||
public string NetworkName { get; private set; }
|
||||
public string NetworkPlacement { get; private set; }
|
||||
public string Placement { get; private set; }
|
||||
public string CreativeIdentifier { get; private set; }
|
||||
public double Revenue { get; private set; }
|
||||
public string RevenuePrecision { get; private set; }
|
||||
public WaterfallInfo WaterfallInfo { get; private set; }
|
||||
public long LatencyMillis { get; private set; }
|
||||
public string DspName { get; private set; }
|
||||
|
||||
public AdInfo(IDictionary<string, object> adInfoDictionary)
|
||||
{
|
||||
AdUnitIdentifier = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "adUnitId");
|
||||
AdFormat = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "adFormat");
|
||||
NetworkName = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "networkName");
|
||||
NetworkPlacement = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "networkPlacement");
|
||||
CreativeIdentifier = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "creativeId");
|
||||
Placement = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "placement");
|
||||
Revenue = MaxSdkUtils.GetDoubleFromDictionary(adInfoDictionary, "revenue", -1);
|
||||
RevenuePrecision = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "revenuePrecision");
|
||||
WaterfallInfo = new WaterfallInfo(MaxSdkUtils.GetDictionaryFromDictionary(adInfoDictionary, "waterfallInfo", new Dictionary<string, object>()));
|
||||
LatencyMillis = MaxSdkUtils.GetLongFromDictionary(adInfoDictionary, "latencyMillis");
|
||||
DspName = MaxSdkUtils.GetStringFromDictionary(adInfoDictionary, "dspName");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[AdInfo adUnitIdentifier: " + AdUnitIdentifier +
|
||||
", adFormat: " + AdFormat +
|
||||
", networkName: " + NetworkName +
|
||||
", networkPlacement: " + NetworkPlacement +
|
||||
", creativeIdentifier: " + CreativeIdentifier +
|
||||
", placement: " + Placement +
|
||||
", revenue: " + Revenue +
|
||||
", revenuePrecision: " + RevenuePrecision +
|
||||
", latency: " + LatencyMillis +
|
||||
", dspName: " + DspName + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns information about the ad response in a waterfall.
|
||||
/// </summary>
|
||||
public class WaterfallInfo
|
||||
{
|
||||
public String Name { get; private set; }
|
||||
public String TestName { get; private set; }
|
||||
public List<NetworkResponseInfo> NetworkResponses { get; private set; }
|
||||
public long LatencyMillis { get; private set; }
|
||||
|
||||
public WaterfallInfo(IDictionary<string, object> waterfallInfoDict)
|
||||
{
|
||||
Name = MaxSdkUtils.GetStringFromDictionary(waterfallInfoDict, "name");
|
||||
TestName = MaxSdkUtils.GetStringFromDictionary(waterfallInfoDict, "testName");
|
||||
|
||||
var networkResponsesList = MaxSdkUtils.GetListFromDictionary(waterfallInfoDict, "networkResponses", new List<object>());
|
||||
NetworkResponses = new List<NetworkResponseInfo>();
|
||||
foreach (var networkResponseObject in networkResponsesList)
|
||||
{
|
||||
var networkResponseDict = networkResponseObject as Dictionary<string, object>;
|
||||
if (networkResponseDict == null) continue;
|
||||
|
||||
var networkResponse = new NetworkResponseInfo(networkResponseDict);
|
||||
NetworkResponses.Add(networkResponse);
|
||||
}
|
||||
|
||||
LatencyMillis = MaxSdkUtils.GetLongFromDictionary(waterfallInfoDict, "latencyMillis");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[MediatedNetworkInfo: name = " + Name +
|
||||
", testName = " + TestName +
|
||||
", latency = " + LatencyMillis +
|
||||
", networkResponse = " + string.Join(", ", NetworkResponses.Select(networkResponseInfo => networkResponseInfo.ToString()).ToArray()) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public class NetworkResponseInfo
|
||||
{
|
||||
public MaxAdLoadState AdLoadState { get; private set; }
|
||||
public MediatedNetworkInfo MediatedNetwork { get; private set; }
|
||||
public Dictionary<string, object> Credentials { get; private set; }
|
||||
public bool IsBidding { get; private set; }
|
||||
public long LatencyMillis { get; private set; }
|
||||
public ErrorInfo Error { get; private set; }
|
||||
|
||||
public NetworkResponseInfo(IDictionary<string, object> networkResponseInfoDict)
|
||||
{
|
||||
var mediatedNetworkInfoDict = MaxSdkUtils.GetDictionaryFromDictionary(networkResponseInfoDict, "mediatedNetwork");
|
||||
MediatedNetwork = mediatedNetworkInfoDict != null ? new MediatedNetworkInfo(mediatedNetworkInfoDict) : null;
|
||||
|
||||
Credentials = MaxSdkUtils.GetDictionaryFromDictionary(networkResponseInfoDict, "credentials", new Dictionary<string, object>());
|
||||
IsBidding = MaxSdkUtils.GetBoolFromDictionary(networkResponseInfoDict, "isBidding");
|
||||
LatencyMillis = MaxSdkUtils.GetLongFromDictionary(networkResponseInfoDict, "latencyMillis");
|
||||
AdLoadState = (MaxAdLoadState) MaxSdkUtils.GetIntFromDictionary(networkResponseInfoDict, "adLoadState");
|
||||
|
||||
var errorInfoDict = MaxSdkUtils.GetDictionaryFromDictionary(networkResponseInfoDict, "error");
|
||||
Error = errorInfoDict != null ? new ErrorInfo(errorInfoDict) : null;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var stringBuilder = new StringBuilder("[NetworkResponseInfo: adLoadState = ").Append(AdLoadState);
|
||||
stringBuilder.Append(", mediatedNetwork = ").Append(MediatedNetwork);
|
||||
stringBuilder.Append(", credentials = ").Append(string.Join(", ", Credentials.Select(keyValuePair => keyValuePair.ToString()).ToArray()));
|
||||
|
||||
switch (AdLoadState)
|
||||
{
|
||||
case MaxAdLoadState.FailedToLoad:
|
||||
stringBuilder.Append(", error = ").Append(Error);
|
||||
break;
|
||||
case MaxAdLoadState.AdLoaded:
|
||||
stringBuilder.Append(", latency = ").Append(LatencyMillis);
|
||||
break;
|
||||
}
|
||||
|
||||
return stringBuilder.Append("]").ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class MediatedNetworkInfo
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public string AdapterClassName { get; private set; }
|
||||
public string AdapterVersion { get; private set; }
|
||||
public string SdkVersion { get; private set; }
|
||||
public InitializationStatus InitializationStatus { get; private set; }
|
||||
|
||||
public MediatedNetworkInfo(IDictionary<string, object> mediatedNetworkDictionary)
|
||||
{
|
||||
// NOTE: Unity Editor creates empty string
|
||||
Name = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "name", "");
|
||||
AdapterClassName = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "adapterClassName", "");
|
||||
AdapterVersion = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "adapterVersion", "");
|
||||
SdkVersion = MaxSdkUtils.GetStringFromDictionary(mediatedNetworkDictionary, "sdkVersion", "");
|
||||
var initializationStatusInt = MaxSdkUtils.GetIntFromDictionary(mediatedNetworkDictionary, "initializationStatus", (int) InitializationStatus.NotInitialized);
|
||||
InitializationStatus = InitializationStatusFromCode(initializationStatusInt);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[MediatedNetworkInfo name: " + Name +
|
||||
", adapterClassName: " + AdapterClassName +
|
||||
", adapterVersion: " + AdapterVersion +
|
||||
", sdkVersion: " + SdkVersion +
|
||||
", initializationStatus: " + InitializationStatus + "]";
|
||||
}
|
||||
|
||||
private static InitializationStatus InitializationStatusFromCode(int code)
|
||||
{
|
||||
if (Enum.IsDefined(typeof(InitializationStatus), code))
|
||||
{
|
||||
return (InitializationStatus) code;
|
||||
}
|
||||
else
|
||||
{
|
||||
return InitializationStatus.NotInitialized;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ErrorInfo
|
||||
{
|
||||
public ErrorCode Code { get; private set; }
|
||||
public string Message { get; private set; }
|
||||
public int MediatedNetworkErrorCode { get; private set; }
|
||||
public string MediatedNetworkErrorMessage { get; private set; }
|
||||
public string AdLoadFailureInfo { get; private set; }
|
||||
public WaterfallInfo WaterfallInfo { get; private set; }
|
||||
public long LatencyMillis { get; private set; }
|
||||
|
||||
public ErrorInfo(IDictionary<string, object> errorInfoDictionary)
|
||||
{
|
||||
Code = (ErrorCode) MaxSdkUtils.GetIntFromDictionary(errorInfoDictionary, "errorCode", -1);
|
||||
Message = MaxSdkUtils.GetStringFromDictionary(errorInfoDictionary, "errorMessage", "");
|
||||
MediatedNetworkErrorCode = MaxSdkUtils.GetIntFromDictionary(errorInfoDictionary, "mediatedNetworkErrorCode", (int) ErrorCode.Unspecified);
|
||||
MediatedNetworkErrorMessage = MaxSdkUtils.GetStringFromDictionary(errorInfoDictionary, "mediatedNetworkErrorMessage", "");
|
||||
AdLoadFailureInfo = MaxSdkUtils.GetStringFromDictionary(errorInfoDictionary, "adLoadFailureInfo", "");
|
||||
WaterfallInfo = new WaterfallInfo(MaxSdkUtils.GetDictionaryFromDictionary(errorInfoDictionary, "waterfallInfo", new Dictionary<string, object>()));
|
||||
LatencyMillis = MaxSdkUtils.GetLongFromDictionary(errorInfoDictionary, "latencyMillis");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var stringbuilder = new StringBuilder("[ErrorInfo code: ").Append(Code);
|
||||
stringbuilder.Append(", message: ").Append(Message);
|
||||
|
||||
if (Code == ErrorCode.AdDisplayFailed)
|
||||
{
|
||||
stringbuilder.Append(", mediatedNetworkCode: ").Append(MediatedNetworkErrorCode);
|
||||
stringbuilder.Append(", mediatedNetworkMessage: ").Append(MediatedNetworkErrorMessage);
|
||||
}
|
||||
|
||||
stringbuilder.Append(", latency: ").Append(LatencyMillis);
|
||||
return stringbuilder.Append(", adLoadFailureInfo: ").Append(AdLoadFailureInfo).Append("]").ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inset values for the safe area on the screen used to render banner ads.
|
||||
/// </summary>
|
||||
public class SafeAreaInsets
|
||||
{
|
||||
public int Left { get; private set; }
|
||||
public int Top { get; private set; }
|
||||
public int Right { get; private set; }
|
||||
public int Bottom { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="SafeAreaInsets"/>.
|
||||
/// </summary>
|
||||
/// <param name="insets">An integer array with insets values in the order of left, top, right, and bottom</param>
|
||||
internal SafeAreaInsets(int[] insets)
|
||||
{
|
||||
Left = insets[0];
|
||||
Top = insets[1];
|
||||
Right = insets[2];
|
||||
Bottom = insets[3];
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[SafeAreaInsets: Left: " + Left +
|
||||
", Top: " + Top +
|
||||
", Right: " + Right +
|
||||
", Bottom: " + Bottom + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether ad events raised by the AppLovin's Unity plugin should be invoked on the Unity main thread.
|
||||
/// </summary>
|
||||
public static bool? InvokeEventsOnUnityMainThread { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The CMP service, which provides direct APIs for interfacing with the Google-certified CMP installed, if any.
|
||||
/// </summary>
|
||||
public static MaxCmpService CmpService
|
||||
{
|
||||
get { return MaxCmpService.Instance; }
|
||||
}
|
||||
|
||||
internal static bool DisableAllLogs
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
protected static void ValidateAdUnitIdentifier(string adUnitIdentifier, string debugPurpose)
|
||||
{
|
||||
if (string.IsNullOrEmpty(adUnitIdentifier))
|
||||
{
|
||||
MaxSdkLogger.UserError("No MAX Ads Ad Unit ID specified for: " + debugPurpose);
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate the MaxEventExecutor singleton which handles pushing callbacks from the background to the main thread.
|
||||
protected static void InitializeEventExecutor()
|
||||
{
|
||||
MaxEventExecutor.InitializeIfNeeded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates serialized Unity meta data to be passed to the SDK.
|
||||
/// </summary>
|
||||
/// <returns>Serialized Unity meta data.</returns>
|
||||
protected static string GenerateMetaData()
|
||||
{
|
||||
var metaData = new Dictionary<string, string>(2);
|
||||
metaData.Add("UnityVersion", Application.unityVersion);
|
||||
|
||||
return Json.Serialize(metaData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the prop string provided to a <see cref="Rect"/>.
|
||||
/// </summary>
|
||||
/// <param name="rectPropString">A prop string representing a Rect</param>
|
||||
/// <returns>A <see cref="Rect"/> the prop string represents.</returns>
|
||||
protected static Rect GetRectFromString(string rectPropString)
|
||||
{
|
||||
var rectDict = Json.Deserialize(rectPropString) as Dictionary<string, object>;
|
||||
var originX = MaxSdkUtils.GetFloatFromDictionary(rectDict, "origin_x", 0);
|
||||
var originY = MaxSdkUtils.GetFloatFromDictionary(rectDict, "origin_y", 0);
|
||||
var width = MaxSdkUtils.GetFloatFromDictionary(rectDict, "width", 0);
|
||||
var height = MaxSdkUtils.GetFloatFromDictionary(rectDict, "height", 0);
|
||||
|
||||
return new Rect(originX, originY, width, height);
|
||||
}
|
||||
|
||||
protected static void HandleExtraParameter(string key, string value)
|
||||
{
|
||||
bool disableAllLogs;
|
||||
if ("disable_all_logs".Equals(key) && bool.TryParse(value, out disableAllLogs))
|
||||
{
|
||||
DisableAllLogs = disableAllLogs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles forwarding callbacks from native to C#.
|
||||
/// </summary>
|
||||
/// <param name="propsStr">A prop string with the event data</param>
|
||||
protected static void HandleBackgroundCallback(string propsStr)
|
||||
{
|
||||
try
|
||||
{
|
||||
MaxSdkCallbacks.ForwardEvent(propsStr);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var eventProps = Json.Deserialize(propsStr) as Dictionary<string, object>;
|
||||
if (eventProps == null) return;
|
||||
|
||||
var eventName = MaxSdkUtils.GetStringFromDictionary(eventProps, "name", "");
|
||||
MaxSdkLogger.UserError("Unable to notify ad delegate due to an error in the publisher callback '" + eventName + "' due to exception: " + exception.Message);
|
||||
MaxSdkLogger.LogException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
protected static string SerializeLocalExtraParameterValue(object value)
|
||||
{
|
||||
if (!(value.GetType().IsPrimitive || value is string || value is IList || value is IDictionary))
|
||||
{
|
||||
MaxSdkLogger.UserError("Local extra parameters must be an IList, IDictionary, string, or a primitive type");
|
||||
return "";
|
||||
}
|
||||
|
||||
Dictionary<string, object> data = new Dictionary<string, object>
|
||||
{
|
||||
{"value", value}
|
||||
};
|
||||
|
||||
return Json.Serialize(data);
|
||||
}
|
||||
|
||||
#region Obsolete
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release. Please use AdViewPosition instead.")]
|
||||
public enum BannerPosition
|
||||
{
|
||||
TopLeft,
|
||||
TopCenter,
|
||||
TopRight,
|
||||
Centered,
|
||||
CenterLeft,
|
||||
CenterRight,
|
||||
BottomLeft,
|
||||
BottomCenter,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
[Obsolete("This API has been deprecated and will be removed in a future release.")]
|
||||
public enum ConsentDialogState
|
||||
{
|
||||
Unknown,
|
||||
Applies,
|
||||
DoesNotApply
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An extension class for <see cref="MaxSdkBase.BannerPosition"/> and <see cref="MaxSdkBase.AdViewPosition"/> enums.
|
||||
/// </summary>
|
||||
internal static class AdPositionExtenstion
|
||||
{
|
||||
public static string ToSnakeCaseString(this MaxSdkBase.AdViewPosition position)
|
||||
{
|
||||
if (position == MaxSdkBase.AdViewPosition.TopLeft)
|
||||
{
|
||||
return "top_left";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.TopCenter)
|
||||
{
|
||||
return "top_center";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.TopRight)
|
||||
{
|
||||
return "top_right";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.Centered)
|
||||
{
|
||||
return "centered";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.CenterLeft)
|
||||
{
|
||||
return "center_left";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.CenterRight)
|
||||
{
|
||||
return "center_right";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.BottomLeft)
|
||||
{
|
||||
return "bottom_left";
|
||||
}
|
||||
else if (position == MaxSdkBase.AdViewPosition.BottomCenter)
|
||||
{
|
||||
return "bottom_center";
|
||||
}
|
||||
else // position == MaxSdkBase.AdViewPosition.BottomRight
|
||||
{
|
||||
return "bottom_right";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 893e4e55a7e394274957f1034f7afc45
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkBase.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4dc129a60049645f7a492a0a658a6c22
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkCallbacks.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class MaxSdkLogger
|
||||
{
|
||||
private const string SdkTag = "AppLovin MAX";
|
||||
public const string KeyVerboseLoggingEnabled = "com.applovin.verbose_logging_enabled";
|
||||
|
||||
/// <summary>
|
||||
/// Log debug messages.
|
||||
/// </summary>
|
||||
public static void UserDebug(string message)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs) return;
|
||||
|
||||
Debug.Log("Debug [" + SdkTag + "] " + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log debug messages when verbose logging is enabled.
|
||||
///
|
||||
/// Verbose logging can be enabled by calling <see cref="MaxSdk.SetVerboseLogging"/> or via the Integration Manager for build time logs.
|
||||
/// </summary>
|
||||
public static void D(string message)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs || !MaxSdk.IsVerboseLoggingEnabled()) return;
|
||||
|
||||
Debug.Log("Debug [" + SdkTag + "] " + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log warning messages.
|
||||
/// </summary>
|
||||
public static void UserWarning(string message)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs) return;
|
||||
|
||||
Debug.LogWarning("Warning [" + SdkTag + "] " + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log warning messages when verbose logging is enabled.
|
||||
///
|
||||
/// Verbose logging can be enabled by calling <see cref="MaxSdk.SetVerboseLogging"/> or via the Integration Manager for build time logs.
|
||||
/// </summary>
|
||||
public static void W(string message)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs || !MaxSdk.IsVerboseLoggingEnabled()) return;
|
||||
|
||||
Debug.LogWarning("Warning [" + SdkTag + "] " + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log error messages.
|
||||
/// </summary>
|
||||
public static void UserError(string message)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs) return;
|
||||
|
||||
Debug.LogError("Error [" + SdkTag + "] " + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log error messages when verbose logging is enabled.
|
||||
///
|
||||
/// Verbose logging can be enabled by calling <see cref="MaxSdk.SetVerboseLogging"/> or via the Integration Manager for build time logs.
|
||||
/// </summary>
|
||||
public static void E(string message)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs || !MaxSdk.IsVerboseLoggingEnabled()) return;
|
||||
|
||||
Debug.LogError("Error [" + SdkTag + "] " + message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log exceptions.
|
||||
/// </summary>
|
||||
public static void LogException(Exception exception)
|
||||
{
|
||||
if (MaxSdk.DisableAllLogs) return;
|
||||
|
||||
Debug.LogException(exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff2b160afdfd4a74b731954323772a90
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkLogger.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13b3e537a64f24986bb8ffe3a0beef5c
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkUnityEditor.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,633 @@
|
||||
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 static class MaxSdkUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// An Enum to be used when comparing two versions.
|
||||
///
|
||||
/// If:
|
||||
/// A < B return <see cref="Lesser"/>
|
||||
/// A == B return <see cref="Equal"/>
|
||||
/// A > 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">Google’s 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;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsVersionInRange(string currentVersion, string minVersion, string maxVersion)
|
||||
{
|
||||
var greaterThanOrEqualToMin = string.IsNullOrEmpty(minVersion) || MaxSdkUtils.CompareVersions(currentVersion, minVersion) != MaxSdkUtils.VersionComparisonResult.Lesser;
|
||||
var lessThanOrEqualToMax = string.IsNullOrEmpty(maxVersion) || MaxSdkUtils.CompareVersions(currentVersion, maxVersion) != MaxSdkUtils.VersionComparisonResult.Greater;
|
||||
return greaterThanOrEqualToMin && lessThanOrEqualToMax;
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given array is null or empty.
|
||||
/// </summary>
|
||||
/// <param name="array">The array to be checked.</param>
|
||||
/// <returns><c>true</c> if the given array is <c>null</c> or has zero length.</returns>
|
||||
public static bool IsNullOrEmpty<T>(T[] array)
|
||||
{
|
||||
return array == null || array.Length == 0;
|
||||
}
|
||||
|
||||
#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 assetLabelToFind = "al_max_export_path-" + NormalizeToUnityPath(exportPath);
|
||||
var labelSearchQuery = "l:" + assetLabelToFind;
|
||||
var assetGuids = AssetDatabase.FindAssets(labelSearchQuery);
|
||||
|
||||
// Search all assets returned from the label query (may include partial matches)
|
||||
foreach (var guid in assetGuids)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var asset = AssetDatabase.LoadMainAssetAtPath(path);
|
||||
if (asset == null) continue;
|
||||
|
||||
var labels = AssetDatabase.GetLabels(asset);
|
||||
|
||||
// Check if any label exactly matches
|
||||
if (labels.Any(label => label == assetLabelToFind)) return path;
|
||||
}
|
||||
|
||||
// Fall back to the default path if no exact label match is found
|
||||
return Path.Combine("Assets", exportPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns a path into a Unity compatible path by replacing backslashes with forward slashes.
|
||||
/// This is important when dealing with Unity's AssetDatabase, which expects paths to use forward slashes.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to normalize</param>
|
||||
/// <returns>A Unity compatible normalized path with only forward slashes.</returns>
|
||||
public static string NormalizeToUnityPath(string path)
|
||||
{
|
||||
return path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47f1bc9a8eebd4d088e22e6baf00ede3
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkUtils.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7c7ed6cdbdca44cd8eae28d3b60d28c
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSdkiOS.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// This class contains a collection of <see cref="MaxSegment"/> objects.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MaxSegmentCollection
|
||||
{
|
||||
[SerializeField] private List<MaxSegment> segments;
|
||||
|
||||
private MaxSegmentCollection(MaxSegmentCollectionBuilder maxSegmentCollectionBuilder)
|
||||
{
|
||||
segments = maxSegmentCollectionBuilder.segments;
|
||||
}
|
||||
|
||||
/// <returns>The list of <see cref="MaxSegment"/> in the <see cref="MaxSegmentCollection"/></returns>
|
||||
public List<MaxSegment> GetSegments()
|
||||
{
|
||||
return segments;
|
||||
}
|
||||
|
||||
public static MaxSegmentCollectionBuilder Builder()
|
||||
{
|
||||
return new MaxSegmentCollectionBuilder();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder class for MaxSegmentCollection.
|
||||
/// </summary>
|
||||
public class MaxSegmentCollectionBuilder
|
||||
{
|
||||
internal readonly List<MaxSegment> segments = new List<MaxSegment>();
|
||||
|
||||
internal MaxSegmentCollectionBuilder() { }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a MaxSegment to the collection.
|
||||
/// </summary>
|
||||
/// <param name="segment">The MaxSegment to add.</param>
|
||||
/// <returns>The MaxSegmentCollectionBuilder instance for chaining.</returns>
|
||||
public MaxSegmentCollectionBuilder AddSegment(MaxSegment segment)
|
||||
{
|
||||
segments.Add(segment);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds and returns the MaxSegmentCollection.
|
||||
/// </summary>
|
||||
/// <returns>The constructed MaxSegmentCollection.</returns>
|
||||
public MaxSegmentCollection Build()
|
||||
{
|
||||
return new MaxSegmentCollection(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class encapsulates a key-value pair, where the key is an int and the value is a List<int>.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MaxSegment
|
||||
{
|
||||
[SerializeField] private int key;
|
||||
[SerializeField] private List<int> values;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="MaxSegment"/> with the specified key and value(s).
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the segment. Must be a non-negative number in the range of [0, 32000].</param>
|
||||
/// <param name="values">The values(s) associated with the key. Each value must be a non-negative number in the range of [0, 32000].</param>
|
||||
public MaxSegment(int key, List<int> values)
|
||||
{
|
||||
this.key = key;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/// <returns>The key of the segment. Must be a non-negative number in the range of [0, 32000].</returns>
|
||||
public int GetKey()
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <returns>The value(s) associated with the key. Each value must be a non-negative number in the range of [0, 32000].</returns>
|
||||
public List<int> GetValues()
|
||||
{
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c1a4950b4ed22489684fa40311f9d5b8
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxSegmentCollection.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// This file has been deprecated and will be removed in a future plugin release.
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2eacd6aa14494de6809b4b06027a332
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxUserServiceAndroid.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// This file has been deprecated and will be removed in a future plugin release.
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16d44e29e68204f36975abec0feb1d35
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxUserServiceUnityEditor.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1 @@
|
||||
// This file has been deprecated and will be removed in a future plugin release.
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6233dd3f0b4664e7dbdca14cc8904176
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxUserServiceiOS.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,319 @@
|
||||
//
|
||||
// MaxWebRequest.cs
|
||||
// AppLovin MAX Unity Plugin
|
||||
//
|
||||
// Created by Jonathan Liu on 6/10/2025.
|
||||
// Copyright © 2025 AppLovin. All rights reserved.
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
using AppLovinMax.ThirdParty.MiniJson;
|
||||
|
||||
namespace AppLovinMax.Internal
|
||||
{
|
||||
public enum WebRequestType
|
||||
{
|
||||
Get,
|
||||
Post
|
||||
}
|
||||
|
||||
public class WebRequestConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Request endpoint. Task will not execute if one is not set.
|
||||
/// </summary>
|
||||
public string EndPoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Request method. GET is used by default.
|
||||
/// </summary>
|
||||
public WebRequestType RequestType { get; set; } = WebRequestType.Get;
|
||||
|
||||
/// <summary>
|
||||
/// The download handler for the web request.
|
||||
/// </summary>
|
||||
public DownloadHandler DownloadHandler { get; set; } = new DownloadHandlerBuffer();
|
||||
|
||||
/// <summary>
|
||||
/// Parameters that will be attached to the request.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> QueryParams { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Headers that will be added to the request.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> Headers { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Request message data that will be sent with the request.
|
||||
/// If both <see cref="Data"/> and <see cref="JsonString"/> are set, <see cref="Data"/> takes precedence and will be serialized to JSON.
|
||||
/// </summary>
|
||||
public object Data { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Request message data in JSON format that will be sent with the request.
|
||||
/// If both <see cref="Data"/> and <see cref="JsonString"/> are set, <see cref="Data"/> takes precedence and will be serialized to JSON.
|
||||
/// </summary>
|
||||
public string JsonString { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The max number of attempts to make the web request before stopping.
|
||||
/// </summary>
|
||||
public int MaxRequestAttempts { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in seconds
|
||||
/// </summary>
|
||||
public int TimeoutSeconds { get; set; } = 60;
|
||||
}
|
||||
|
||||
public class WebResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the request succeeded.
|
||||
/// </summary>
|
||||
public bool IsSuccess { get; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The completed UnityWebRequest.
|
||||
/// </summary>
|
||||
public string ResponseMessage { get; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// The error message if the request failed.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; } = "";
|
||||
|
||||
public WebResponse(UnityWebRequest request)
|
||||
{
|
||||
if (request == null) return;
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
IsSuccess = request.result == UnityWebRequest.Result.Success;
|
||||
#else
|
||||
IsSuccess = !(request.isNetworkError || request.isHttpError);
|
||||
#endif
|
||||
// Only DownloadHandlerBuffer should try to access the text
|
||||
if (request.downloadHandler is DownloadHandlerBuffer)
|
||||
{
|
||||
ResponseMessage = request.downloadHandler.text;
|
||||
}
|
||||
|
||||
ErrorMessage = request.error;
|
||||
}
|
||||
}
|
||||
|
||||
public class MaxWebRequest
|
||||
{
|
||||
private const int WaitBetweenRetriesSeconds = 1;
|
||||
private readonly WebRequestConfig webRequestConfig;
|
||||
private UnityWebRequest webRequest;
|
||||
private bool isSending;
|
||||
|
||||
public MaxWebRequest(WebRequestConfig config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
MaxSdkLogger.E("WebRequestConfig cannot be null. Please provide a valid configuration.");
|
||||
return;
|
||||
}
|
||||
|
||||
webRequestConfig = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a web request using coroutines.
|
||||
/// </summary>
|
||||
/// <param name="callback">
|
||||
/// A callback invoked with the resulting <see cref="WebResponse"/> object.
|
||||
/// </param>
|
||||
public IEnumerator Send(Action<WebResponse> callback)
|
||||
{
|
||||
yield return SendInternal(
|
||||
request => request.SendAndWait(),
|
||||
response => { callback?.Invoke(response); });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a web request synchronously and returns the response.
|
||||
/// </summary>
|
||||
/// <returns>Returns a <see cref="WebResponse"/> object.</returns>
|
||||
public WebResponse SendSync()
|
||||
{
|
||||
var finalResponse = new WebResponse(null);
|
||||
|
||||
SendInternal(
|
||||
waitFunc: request =>
|
||||
{
|
||||
request.SendWebRequest();
|
||||
while (!request.isDone) { } // Block until the request is done
|
||||
|
||||
return null; // We don't use IEnumerator for sync version
|
||||
},
|
||||
onComplete: response => finalResponse = response
|
||||
).MoveNext(); // Needed to start the loop (since it's still an IEnumerator)
|
||||
|
||||
return finalResponse;
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
{
|
||||
if (webRequest != null && !webRequest.isDone)
|
||||
{
|
||||
webRequest.Abort();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a web request using the current WebRequestConfig with automatic retries on failure.
|
||||
/// </summary>
|
||||
/// <param name="waitFunc">
|
||||
/// The function to use for waiting on the web request to complete.
|
||||
/// </param>
|
||||
/// /// <param name="onComplete">
|
||||
/// The callback invoked when the web request completes, with a <see cref="WebResponse"/> object containing the result.
|
||||
/// </param>
|
||||
private IEnumerator SendInternal(Func<UnityWebRequest, IEnumerator> waitFunc, Action<WebResponse> onComplete)
|
||||
{
|
||||
if (isSending || string.IsNullOrEmpty(webRequestConfig.EndPoint))
|
||||
{
|
||||
var errorString = isSending ? "Web Request currently being sent. Please send another request after the current one has finished." : "Web request endpoint is null or empty.";
|
||||
MaxSdkLogger.E(errorString);
|
||||
onComplete(new WebResponse(null));
|
||||
}
|
||||
|
||||
isSending = true;
|
||||
try
|
||||
{
|
||||
for (var attempt = 1; attempt <= webRequestConfig.MaxRequestAttempts; attempt++)
|
||||
{
|
||||
using (var request = CreateWebRequest())
|
||||
{
|
||||
// Hold a reference to the request so we can Abort the request if needed.
|
||||
webRequest = request;
|
||||
|
||||
var wait = waitFunc(request);
|
||||
if (wait != null)
|
||||
yield return wait;
|
||||
|
||||
var webResponse = new WebResponse(request);
|
||||
|
||||
if (webResponse.IsSuccess)
|
||||
{
|
||||
onComplete(webResponse);
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (attempt < webRequestConfig.MaxRequestAttempts)
|
||||
{
|
||||
MaxSdkLogger.UserWarning($"Error: {request.error}, Attempt {attempt} failed... Retrying request");
|
||||
}
|
||||
else
|
||||
{
|
||||
// All attempts have failed. Send error callback.
|
||||
MaxSdkLogger.UserError($"Failed to make web request after {webRequestConfig.MaxRequestAttempts} attempts.");
|
||||
onComplete(webResponse);
|
||||
}
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(WaitBetweenRetriesSeconds);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
webRequest = null;
|
||||
isSending = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a web request using the given configuration.
|
||||
/// </summary>
|
||||
/// <returns>Returns the web request that was created using the instance's WebRequestConfiguration.</returns>
|
||||
private UnityWebRequest CreateWebRequest()
|
||||
{
|
||||
var url = BuildURL();
|
||||
var request = new UnityWebRequest(url, webRequestConfig.RequestType.ToHttpMethodString())
|
||||
{
|
||||
downloadHandler = webRequestConfig.DownloadHandler,
|
||||
timeout = webRequestConfig.TimeoutSeconds
|
||||
};
|
||||
|
||||
// Set request upload data if needed
|
||||
if (webRequestConfig.Data != null || MaxSdkUtils.IsValidString(webRequestConfig.JsonString))
|
||||
{
|
||||
var jsonString = webRequestConfig.Data != null ? Json.Serialize(webRequestConfig.Data) : webRequestConfig.JsonString;
|
||||
var rawData = Encoding.UTF8.GetBytes(jsonString);
|
||||
request.uploadHandler = new UploadHandlerRaw(rawData);
|
||||
}
|
||||
|
||||
// Set request headers
|
||||
foreach (var header in webRequestConfig.Headers)
|
||||
{
|
||||
request.SetRequestHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a URL with the endpoint and query parameters from the instance's WebRequestConfiguration.
|
||||
/// </summary>
|
||||
/// <returns>Returns a formatted URL built using the endpoint and query parameters.</returns>
|
||||
private string BuildURL()
|
||||
{
|
||||
if (webRequestConfig.QueryParams.Count == 0) return webRequestConfig.EndPoint;
|
||||
|
||||
var uriBuilder = new UriBuilder(webRequestConfig.EndPoint);
|
||||
uriBuilder.Query = webRequestConfig.QueryParams.ToQueryString();
|
||||
return uriBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MaxWebRequestExtension
|
||||
{
|
||||
internal static IEnumerator SendAndWait(this UnityWebRequest request)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var operation = request.SendWebRequest();
|
||||
|
||||
// In the Unity Editor, `yield return request.SendWebRequest()` fails, so we manually poll `isDone` in a loop.
|
||||
while (!operation.isDone) yield return new WaitForSeconds(0.1f);
|
||||
#else
|
||||
yield return request.SendWebRequest();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static string ToHttpMethodString(this WebRequestType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case WebRequestType.Get:
|
||||
return "GET";
|
||||
case WebRequestType.Post:
|
||||
return "POST";
|
||||
default:
|
||||
return "GET";
|
||||
}
|
||||
}
|
||||
|
||||
internal static string ToQueryString(this Dictionary<string, string> queries)
|
||||
{
|
||||
var queryBuilder = new StringBuilder();
|
||||
foreach (var query in queries)
|
||||
{
|
||||
if (query.Key == null || query.Value == null) continue;
|
||||
|
||||
queryBuilder.Append(queryBuilder.Length == 0 ? "?" : "&");
|
||||
queryBuilder.AppendFormat("{0}={1}", Uri.EscapeDataString(query.Key), Uri.EscapeDataString(query.Value));
|
||||
}
|
||||
|
||||
return queryBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f1b03fdc97184fb98b605fe27346916
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/MaxWebRequest.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68fc43fe05dea4106bb4b376720d6708
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/ThirdParty
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+683
@@ -0,0 +1,683 @@
|
||||
/*
|
||||
* Copyright (c) 2013 Calvin Rien
|
||||
*
|
||||
* Based on the JSON parser by Patrick van Bergen
|
||||
* http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html
|
||||
*
|
||||
* Simplified it so that it doesn't throw exceptions
|
||||
* and can be used in Unity iPhone with maximum code stripping.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace AppLovinMax.ThirdParty.MiniJson
|
||||
{
|
||||
// Example usage:
|
||||
//
|
||||
// using UnityEngine;
|
||||
// using System.Collections;
|
||||
// using System.Collections.Generic;
|
||||
// using MiniJSON;
|
||||
//
|
||||
// public class MiniJSONTest : MonoBehaviour {
|
||||
// void Start () {
|
||||
// var jsonString = "{ \"array\": [1.44,2,3], " +
|
||||
// "\"object\": {\"key1\":\"value1\", \"key2\":256}, " +
|
||||
// "\"string\": \"The quick brown fox \\\"jumps\\\" over the lazy dog \", " +
|
||||
// "\"unicode\": \"\\u3041 Men\u00fa sesi\u00f3n\", " +
|
||||
// "\"int\": 65536, " +
|
||||
// "\"float\": 3.1415926, " +
|
||||
// "\"bool\": true, " +
|
||||
// "\"null\": null }";
|
||||
//
|
||||
// var dict = Json.Deserialize(jsonString) as Dictionary<string,object>;
|
||||
//
|
||||
// Debug.Log("deserialized: " + dict.GetType());
|
||||
// Debug.Log("dict['array'][0]: " + ((List<object>) dict["array"])[0]);
|
||||
// Debug.Log("dict['string']: " + (string) dict["string"]);
|
||||
// Debug.Log("dict['float']: " + (double) dict["float"]); // floats come out as doubles
|
||||
// Debug.Log("dict['int']: " + (long) dict["int"]); // ints come out as longs
|
||||
// Debug.Log("dict['unicode']: " + (string) dict["unicode"]);
|
||||
//
|
||||
// var str = Json.Serialize(dict);
|
||||
//
|
||||
// Debug.Log("serialized: " + str);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// This class encodes and decodes JSON strings.
|
||||
/// Spec. details, see http://www.json.org/
|
||||
///
|
||||
/// JSON uses Arrays and Objects. These correspond here to the datatypes IList and IDictionary.
|
||||
/// All numbers are parsed to doubles.
|
||||
/// </summary>
|
||||
public static class Json
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the string json into a value
|
||||
/// </summary>
|
||||
/// <param name="json">A JSON string.</param>
|
||||
/// <returns>An List<object>, a Dictionary<string, object>, a double, an integer,a string, null, true, or false</returns>
|
||||
public static object Deserialize(string json)
|
||||
{
|
||||
// save the string for debug information
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Parser.Parse(json);
|
||||
}
|
||||
|
||||
sealed class Parser : IDisposable
|
||||
{
|
||||
const string WORD_BREAK = "{}[],:\"";
|
||||
|
||||
public static bool IsWordBreak(char c)
|
||||
{
|
||||
return Char.IsWhiteSpace(c) || WORD_BREAK.IndexOf(c) != -1;
|
||||
}
|
||||
|
||||
enum TOKEN
|
||||
{
|
||||
NONE,
|
||||
CURLY_OPEN,
|
||||
CURLY_CLOSE,
|
||||
SQUARED_OPEN,
|
||||
SQUARED_CLOSE,
|
||||
COLON,
|
||||
COMMA,
|
||||
STRING,
|
||||
NUMBER,
|
||||
TRUE,
|
||||
FALSE,
|
||||
NULL
|
||||
};
|
||||
|
||||
StringReader json;
|
||||
|
||||
Parser(string jsonString)
|
||||
{
|
||||
json = new StringReader(jsonString);
|
||||
}
|
||||
|
||||
public static object Parse(string jsonString)
|
||||
{
|
||||
using (var instance = new Parser(jsonString))
|
||||
{
|
||||
return instance.ParseValue();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
json.Dispose();
|
||||
json = null;
|
||||
}
|
||||
|
||||
Dictionary<string, object> ParseObject()
|
||||
{
|
||||
Dictionary<string, object> table = new Dictionary<string, object>();
|
||||
|
||||
// ditch opening brace
|
||||
json.Read();
|
||||
|
||||
// {
|
||||
while (true)
|
||||
{
|
||||
switch (NextToken)
|
||||
{
|
||||
case TOKEN.NONE:
|
||||
return null;
|
||||
case TOKEN.COMMA:
|
||||
continue;
|
||||
case TOKEN.CURLY_CLOSE:
|
||||
return table;
|
||||
default:
|
||||
// name
|
||||
string name = ParseString();
|
||||
if (name == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// :
|
||||
if (NextToken != TOKEN.COLON)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// ditch the colon
|
||||
json.Read();
|
||||
|
||||
// value
|
||||
table[name] = ParseValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<object> ParseArray()
|
||||
{
|
||||
List<object> array = new List<object>();
|
||||
|
||||
// ditch opening bracket
|
||||
json.Read();
|
||||
|
||||
// [
|
||||
var parsing = true;
|
||||
while (parsing)
|
||||
{
|
||||
TOKEN nextToken = NextToken;
|
||||
|
||||
switch (nextToken)
|
||||
{
|
||||
case TOKEN.NONE:
|
||||
return null;
|
||||
case TOKEN.COMMA:
|
||||
continue;
|
||||
case TOKEN.SQUARED_CLOSE:
|
||||
parsing = false;
|
||||
break;
|
||||
default:
|
||||
object value = ParseByToken(nextToken);
|
||||
|
||||
array.Add(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
object ParseValue()
|
||||
{
|
||||
TOKEN nextToken = NextToken;
|
||||
return ParseByToken(nextToken);
|
||||
}
|
||||
|
||||
object ParseByToken(TOKEN token)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case TOKEN.STRING:
|
||||
return ParseString();
|
||||
case TOKEN.NUMBER:
|
||||
return ParseNumber();
|
||||
case TOKEN.CURLY_OPEN:
|
||||
return ParseObject();
|
||||
case TOKEN.SQUARED_OPEN:
|
||||
return ParseArray();
|
||||
case TOKEN.TRUE:
|
||||
return true;
|
||||
case TOKEN.FALSE:
|
||||
return false;
|
||||
case TOKEN.NULL:
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string ParseString()
|
||||
{
|
||||
StringBuilder s = new StringBuilder();
|
||||
char c;
|
||||
|
||||
// ditch opening quote
|
||||
json.Read();
|
||||
|
||||
bool parsing = true;
|
||||
while (parsing)
|
||||
{
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
parsing = false;
|
||||
break;
|
||||
}
|
||||
|
||||
c = NextChar;
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
parsing = false;
|
||||
break;
|
||||
case '\\':
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
parsing = false;
|
||||
break;
|
||||
}
|
||||
|
||||
c = NextChar;
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
s.Append(c);
|
||||
break;
|
||||
case 'b':
|
||||
s.Append('\b');
|
||||
break;
|
||||
case 'f':
|
||||
s.Append('\f');
|
||||
break;
|
||||
case 'n':
|
||||
s.Append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
s.Append('\r');
|
||||
break;
|
||||
case 't':
|
||||
s.Append('\t');
|
||||
break;
|
||||
case 'u':
|
||||
var hex = new char[4];
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
hex[i] = NextChar;
|
||||
}
|
||||
|
||||
s.Append((char) Convert.ToInt32(new string(hex), 16));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
s.Append(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
object ParseNumber()
|
||||
{
|
||||
string number = NextWord;
|
||||
|
||||
if (number.IndexOf('.') == -1)
|
||||
{
|
||||
long parsedInt;
|
||||
Int64.TryParse(number, out parsedInt);
|
||||
return parsedInt;
|
||||
}
|
||||
|
||||
double parsedDouble;
|
||||
Double.TryParse(number, out parsedDouble);
|
||||
return parsedDouble;
|
||||
}
|
||||
|
||||
void EatWhitespace()
|
||||
{
|
||||
while (Char.IsWhiteSpace(PeekChar))
|
||||
{
|
||||
json.Read();
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char PeekChar
|
||||
{
|
||||
get { return Convert.ToChar(json.Peek()); }
|
||||
}
|
||||
|
||||
char NextChar
|
||||
{
|
||||
get { return Convert.ToChar(json.Read()); }
|
||||
}
|
||||
|
||||
string NextWord
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder word = new StringBuilder();
|
||||
|
||||
while (!IsWordBreak(PeekChar))
|
||||
{
|
||||
word.Append(NextChar);
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return word.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
TOKEN NextToken
|
||||
{
|
||||
get
|
||||
{
|
||||
EatWhitespace();
|
||||
|
||||
if (json.Peek() == -1)
|
||||
{
|
||||
return TOKEN.NONE;
|
||||
}
|
||||
|
||||
switch (PeekChar)
|
||||
{
|
||||
case '{':
|
||||
return TOKEN.CURLY_OPEN;
|
||||
case '}':
|
||||
json.Read();
|
||||
return TOKEN.CURLY_CLOSE;
|
||||
case '[':
|
||||
return TOKEN.SQUARED_OPEN;
|
||||
case ']':
|
||||
json.Read();
|
||||
return TOKEN.SQUARED_CLOSE;
|
||||
case ',':
|
||||
json.Read();
|
||||
return TOKEN.COMMA;
|
||||
case '"':
|
||||
return TOKEN.STRING;
|
||||
case ':':
|
||||
return TOKEN.COLON;
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '-':
|
||||
return TOKEN.NUMBER;
|
||||
}
|
||||
|
||||
switch (NextWord)
|
||||
{
|
||||
case "false":
|
||||
return TOKEN.FALSE;
|
||||
case "true":
|
||||
return TOKEN.TRUE;
|
||||
case "null":
|
||||
return TOKEN.NULL;
|
||||
}
|
||||
|
||||
return TOKEN.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a IDictionary / IList object or a simple type (string, int, etc.) into a JSON string
|
||||
/// </summary>
|
||||
/// <param name="obj">A Dictionary<string, object> / List<object></param>
|
||||
/// <param name="prettyPrint">Whether to pretty print the json string</param>
|
||||
/// <returns>A JSON encoded string, or null if object 'json' is not serializable</returns>
|
||||
public static string Serialize(object obj, bool prettyPrint = false)
|
||||
{
|
||||
return Serializer.Serialize(obj, prettyPrint);
|
||||
}
|
||||
|
||||
sealed class Serializer
|
||||
{
|
||||
StringBuilder builder;
|
||||
bool prettyPrint;
|
||||
int indentLevel;
|
||||
|
||||
Serializer(bool prettyPrint)
|
||||
{
|
||||
builder = new StringBuilder();
|
||||
this.prettyPrint = prettyPrint;
|
||||
indentLevel = 0;
|
||||
}
|
||||
|
||||
public static string Serialize(object obj, bool prettyPrint)
|
||||
{
|
||||
var instance = new Serializer(prettyPrint);
|
||||
|
||||
instance.SerializeValue(obj);
|
||||
|
||||
return instance.builder.ToString();
|
||||
}
|
||||
|
||||
void SerializeValue(object value)
|
||||
{
|
||||
IList asList;
|
||||
IDictionary asDict;
|
||||
string asStr;
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
builder.Append("null");
|
||||
}
|
||||
else if ((asStr = value as string) != null)
|
||||
{
|
||||
SerializeString(asStr);
|
||||
}
|
||||
else if (value is bool)
|
||||
{
|
||||
builder.Append((bool) value ? "true" : "false");
|
||||
}
|
||||
else if ((asList = value as IList) != null)
|
||||
{
|
||||
SerializeArray(asList);
|
||||
}
|
||||
else if ((asDict = value as IDictionary) != null)
|
||||
{
|
||||
SerializeObject(asDict);
|
||||
}
|
||||
else if (value is char)
|
||||
{
|
||||
SerializeString(new string((char) value, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeOther(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SerializeObject(IDictionary obj)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
builder.Append('{');
|
||||
|
||||
indentLevel++;
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
foreach (object e in obj.Keys)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
builder.Append(',');
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.Append(new string(' ', indentLevel * 4));
|
||||
}
|
||||
|
||||
SerializeString(e.ToString());
|
||||
builder.Append(':');
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.Append(' ');
|
||||
}
|
||||
|
||||
SerializeValue(obj[e]);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
indentLevel--;
|
||||
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.Append(new string(' ', indentLevel * 4));
|
||||
}
|
||||
|
||||
builder.Append('}');
|
||||
}
|
||||
|
||||
void SerializeArray(IList anArray)
|
||||
{
|
||||
builder.Append('[');
|
||||
|
||||
indentLevel++;
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
|
||||
foreach (object obj in anArray)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
builder.Append(',');
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.Append(new string(' ', indentLevel * 4));
|
||||
}
|
||||
|
||||
SerializeValue(obj);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
indentLevel--;
|
||||
if (prettyPrint)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.Append(new string(' ', indentLevel * 4));
|
||||
}
|
||||
|
||||
builder.Append(']');
|
||||
}
|
||||
|
||||
void SerializeString(string str)
|
||||
{
|
||||
builder.Append('\"');
|
||||
|
||||
char[] charArray = str.ToCharArray();
|
||||
foreach (var c in charArray)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
builder.Append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
builder.Append("\\\\");
|
||||
break;
|
||||
case '\b':
|
||||
builder.Append("\\b");
|
||||
break;
|
||||
case '\f':
|
||||
builder.Append("\\f");
|
||||
break;
|
||||
case '\n':
|
||||
builder.Append("\\n");
|
||||
break;
|
||||
case '\r':
|
||||
builder.Append("\\r");
|
||||
break;
|
||||
case '\t':
|
||||
builder.Append("\\t");
|
||||
break;
|
||||
default:
|
||||
int codepoint = Convert.ToInt32(c);
|
||||
if ((codepoint >= 32) && (codepoint <= 126))
|
||||
{
|
||||
builder.Append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("\\u");
|
||||
builder.Append(codepoint.ToString("x4"));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append('\"');
|
||||
}
|
||||
|
||||
void SerializeOther(object value)
|
||||
{
|
||||
// NOTE: decimals lose precision during serialization.
|
||||
// They always have, I'm just letting you know.
|
||||
// Previously floats and doubles lost precision too.
|
||||
if (value is float)
|
||||
{
|
||||
builder.Append(((float) value).ToString("R"));
|
||||
}
|
||||
else if (value is int
|
||||
|| value is uint
|
||||
|| value is long
|
||||
|| value is sbyte
|
||||
|| value is byte
|
||||
|| value is short
|
||||
|| value is ushort
|
||||
|| value is ulong)
|
||||
{
|
||||
builder.Append(value);
|
||||
}
|
||||
else if (value is double
|
||||
|| value is decimal)
|
||||
{
|
||||
builder.Append(Convert.ToDouble(value).ToString("R"));
|
||||
}
|
||||
else
|
||||
{
|
||||
SerializeString(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52789a1d5a5754d46816fd9ed114a375
|
||||
labels:
|
||||
- al_max
|
||||
- al_max_export_path-MaxSdk/Scripts/ThirdParty/MiniJSON.cs
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user