AppLovinIntegrationManager.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. //
  2. // MaxIntegrationManager.cs
  3. // AppLovin MAX Unity Plugin
  4. //
  5. // Created by Santosh Bagadi on 6/1/19.
  6. // Copyright © 2019 AppLovin. All rights reserved.
  7. //
  8. using System;
  9. using System.Collections;
  10. using System.IO;
  11. using UnityEditor;
  12. using UnityEngine;
  13. using UnityEngine.Networking;
  14. namespace AppLovinMax.Scripts.IntegrationManager.Editor
  15. {
  16. [Serializable]
  17. public class PluginData
  18. {
  19. // ReSharper disable InconsistentNaming - Consistent with JSON data.
  20. public Network AppLovinMax;
  21. public Network[] MediatedNetworks;
  22. public Network[] PartnerMicroSdks;
  23. public DynamicLibraryToEmbed[] ThirdPartyDynamicLibrariesToEmbed;
  24. }
  25. [Serializable]
  26. public class Network
  27. {
  28. //
  29. // Sample network data:
  30. //
  31. // {
  32. // "Name": "adcolony",
  33. // "DisplayName": "AdColony",
  34. // "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",
  35. // "PluginFileName": "AppLovin-AdColony-Adapters-Android-3.3.10.1-iOS-3.3.7.2.unitypackage",
  36. // "DependenciesFilePath": "MaxSdk/Mediation/AdColony/Editor/Dependencies.xml",
  37. // "LatestVersions" : {
  38. // "Unity": "android_3.3.10.1_ios_3.3.7.2",
  39. // "Android": "3.3.10.1",
  40. // "Ios": "3.3.7.2"
  41. // }
  42. // }
  43. //
  44. // ReSharper disable InconsistentNaming - Consistent with JSON data.
  45. public string Name;
  46. public string DisplayName;
  47. public string DownloadUrl;
  48. public string DependenciesFilePath;
  49. public PackageInfo[] Packages;
  50. public string[] PluginFilePaths;
  51. public Versions LatestVersions;
  52. public DynamicLibraryToEmbed[] DynamicLibrariesToEmbed;
  53. [NonSerialized] public Versions CurrentVersions;
  54. [NonSerialized] public MaxSdkUtils.VersionComparisonResult CurrentToLatestVersionComparisonResult = MaxSdkUtils.VersionComparisonResult.Lesser;
  55. [NonSerialized] public bool RequiresUpdate;
  56. }
  57. [Serializable]
  58. public class DynamicLibraryToEmbed
  59. {
  60. // ReSharper disable InconsistentNaming - Consistent with JSON data.
  61. public string PodName;
  62. public string[] FrameworkNames;
  63. // Min and max versions are inclusive, so if the adapter is the min or max version, the xcframework will get embedded.
  64. public string MinVersion;
  65. public string MaxVersion;
  66. public DynamicLibraryToEmbed(string podName, string[] frameworkNames, string minVersion, string maxVersion)
  67. {
  68. PodName = podName;
  69. FrameworkNames = frameworkNames;
  70. MinVersion = minVersion;
  71. MaxVersion = maxVersion;
  72. }
  73. }
  74. /// <summary>
  75. /// A helper data class used to get current versions from Dependency.xml files.
  76. /// </summary>
  77. [Serializable]
  78. public class Versions
  79. {
  80. // ReSharper disable InconsistentNaming - Consistent with JSON data.
  81. public string Unity;
  82. public string Android;
  83. public string Ios;
  84. public override bool Equals(object value)
  85. {
  86. var versions = value as Versions;
  87. return versions != null
  88. && Unity.Equals(versions.Unity)
  89. && (Android == null || Android.Equals(versions.Android))
  90. && (Ios == null || Ios.Equals(versions.Ios));
  91. }
  92. public bool HasEqualSdkVersions(Versions versions)
  93. {
  94. return versions != null
  95. && AdapterSdkVersion(Android).Equals(AdapterSdkVersion(versions.Android))
  96. && AdapterSdkVersion(Ios).Equals(AdapterSdkVersion(versions.Ios));
  97. }
  98. public override int GetHashCode()
  99. {
  100. return new {unity = Unity, android = Android, ios = Ios}.GetHashCode();
  101. }
  102. private static string AdapterSdkVersion(string adapterVersion)
  103. {
  104. if (string.IsNullOrEmpty(adapterVersion)) return "";
  105. var index = adapterVersion.LastIndexOf(".", StringComparison.Ordinal);
  106. return index > 0 ? adapterVersion.Substring(0, index) : adapterVersion;
  107. }
  108. }
  109. /// <summary>
  110. /// A manager class for MAX integration manager window.
  111. /// </summary>
  112. public class AppLovinIntegrationManager
  113. {
  114. /// <summary>
  115. /// Delegate to be called when downloading a plugin with the progress percentage.
  116. /// </summary>
  117. /// <param name="pluginName">The name of the plugin being downloaded.</param>
  118. /// <param name="progress">Percentage downloaded.</param>
  119. /// <param name="done">Whether or not the download is complete.</param>
  120. public delegate void DownloadPluginProgressCallback(string pluginName, float progress, bool done);
  121. /// <summary>
  122. /// Delegate to be called when a plugin package is imported.
  123. /// </summary>
  124. /// <param name="network">The network data for which the package is imported.</param>
  125. public delegate void ImportPackageCompletedCallback(Network network);
  126. private static readonly AppLovinIntegrationManager instance = new AppLovinIntegrationManager();
  127. internal static readonly string GradleTemplatePath = Path.Combine("Assets/Plugins/Android", "mainTemplate.gradle");
  128. private const string MaxSdkAssetExportPath = "MaxSdk/Scripts/MaxSdk.cs";
  129. private const string MaxSdkMediationExportPath = "MaxSdk/Mediation";
  130. private static readonly string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}";
  131. private static string _externalDependencyManagerVersion;
  132. public static DownloadPluginProgressCallback OnDownloadPluginProgressCallback;
  133. public static ImportPackageCompletedCallback OnImportPackageCompletedCallback;
  134. private UnityWebRequest webRequest;
  135. private Network importingNetwork;
  136. /// <summary>
  137. /// An Instance of the Integration manager.
  138. /// </summary>
  139. public static AppLovinIntegrationManager Instance
  140. {
  141. get { return instance; }
  142. }
  143. /// <summary>
  144. /// The parent directory path where the MaxSdk plugin directory is placed.
  145. /// </summary>
  146. public static string PluginParentDirectory
  147. {
  148. get
  149. {
  150. // Search for the asset with the default exported path first, In most cases, we should be able to find the asset.
  151. // In some cases where we don't, use the platform specific export path to search for the asset (in case of migrating a project from Windows to Mac or vice versa).
  152. var maxSdkScriptAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkAssetExportPath);
  153. // maxSdkScriptAssetPath will always have AltDirectorySeparatorChar (/) as the path separator. Convert to platform specific path.
  154. return maxSdkScriptAssetPath.Replace(MaxSdkAssetExportPath, "")
  155. .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
  156. }
  157. }
  158. public static string MediationDirectory
  159. {
  160. get
  161. {
  162. var mediationAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkMediationExportPath);
  163. return mediationAssetPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
  164. }
  165. }
  166. /// <summary>
  167. /// Whether or not the plugin is in the Unity Package Manager.
  168. /// </summary>
  169. public static bool IsPluginInPackageManager
  170. {
  171. get { return PluginParentDirectory.StartsWith("Packages"); }
  172. }
  173. /// <summary>
  174. /// Whether or not gradle build system is enabled.
  175. /// </summary>
  176. public static bool GradleBuildEnabled
  177. {
  178. get { return GetEditorUserBuildSetting("androidBuildSystem", "").ToString().Equals("Gradle"); }
  179. }
  180. /// <summary>
  181. /// Whether or not Gradle template is enabled.
  182. /// </summary>
  183. public static bool GradleTemplateEnabled
  184. {
  185. get { return GradleBuildEnabled && File.Exists(GradleTemplatePath); }
  186. }
  187. /// <summary>
  188. /// Whether or not the Quality Service settings can be processed which requires Gradle template enabled or Unity IDE newer than version 2018_2.
  189. /// </summary>
  190. public static bool CanProcessAndroidQualityServiceSettings
  191. {
  192. get { return GradleTemplateEnabled || GradleBuildEnabled; }
  193. }
  194. /// <summary>
  195. /// The External Dependency Manager version obtained dynamically.
  196. /// </summary>
  197. public static string ExternalDependencyManagerVersion
  198. {
  199. get
  200. {
  201. if (MaxSdkUtils.IsValidString(_externalDependencyManagerVersion)) return _externalDependencyManagerVersion;
  202. try
  203. {
  204. var versionHandlerVersionNumberType = Type.GetType("Google.VersionHandlerVersionNumber, Google.VersionHandlerImpl");
  205. _externalDependencyManagerVersion = versionHandlerVersionNumberType.GetProperty("Value").GetValue(null, null).ToString();
  206. }
  207. #pragma warning disable 0168
  208. catch (Exception ignored)
  209. #pragma warning restore 0168
  210. {
  211. _externalDependencyManagerVersion = "Failed to get version.";
  212. }
  213. return _externalDependencyManagerVersion;
  214. }
  215. }
  216. private AppLovinIntegrationManager()
  217. {
  218. // Add asset import callbacks.
  219. AssetDatabase.importPackageCompleted += packageName =>
  220. {
  221. if (!IsImportingNetwork(packageName)) return;
  222. AssetDatabase.Refresh();
  223. CallImportPackageCompletedCallback(importingNetwork);
  224. importingNetwork = null;
  225. };
  226. AssetDatabase.importPackageCancelled += packageName =>
  227. {
  228. if (!IsImportingNetwork(packageName)) return;
  229. MaxSdkLogger.UserDebug("Package import cancelled.");
  230. importingNetwork = null;
  231. };
  232. AssetDatabase.importPackageFailed += (packageName, errorMessage) =>
  233. {
  234. if (!IsImportingNetwork(packageName)) return;
  235. MaxSdkLogger.UserError(errorMessage);
  236. importingNetwork = null;
  237. };
  238. }
  239. static AppLovinIntegrationManager() { }
  240. public static PluginData LoadPluginDataSync()
  241. {
  242. var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
  243. using (var unityWebRequest = UnityWebRequest.Get(url))
  244. {
  245. var operation = unityWebRequest.SendWebRequest();
  246. // Just wait till www is done
  247. while (!operation.isDone) { }
  248. return CreatePluginDataFromWebResponse(unityWebRequest);
  249. }
  250. }
  251. /// <summary>
  252. /// Loads the plugin data to be display by integration manager window.
  253. /// </summary>
  254. /// <param name="callback">Callback to be called once the plugin data download completes.</param>
  255. public IEnumerator LoadPluginData(Action<PluginData> callback)
  256. {
  257. var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
  258. using (var unityWebRequest = UnityWebRequest.Get(url))
  259. {
  260. var operation = unityWebRequest.SendWebRequest();
  261. while (!operation.isDone) yield return new WaitForSeconds(0.1f); // Just wait till www is done. Our coroutine is pretty rudimentary.
  262. var pluginData = CreatePluginDataFromWebResponse(unityWebRequest);
  263. callback(pluginData);
  264. }
  265. }
  266. private static PluginData CreatePluginDataFromWebResponse(UnityWebRequest unityWebRequest)
  267. {
  268. #if UNITY_2020_1_OR_NEWER
  269. if (unityWebRequest.result != UnityWebRequest.Result.Success)
  270. #else
  271. if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
  272. #endif
  273. {
  274. MaxSdkLogger.E("Failed to load plugin data. Please check your internet connection.");
  275. return null;
  276. }
  277. PluginData pluginData;
  278. try
  279. {
  280. pluginData = JsonUtility.FromJson<PluginData>(unityWebRequest.downloadHandler.text);
  281. AppLovinPackageManager.PluginData = pluginData;
  282. }
  283. catch (Exception exception)
  284. {
  285. Console.WriteLine(exception);
  286. pluginData = null;
  287. }
  288. if (pluginData == null) return null;
  289. // Get current version of the plugin
  290. var appLovinMax = pluginData.AppLovinMax;
  291. AppLovinPackageManager.UpdateCurrentVersions(appLovinMax);
  292. // Get current versions for all the mediation networks.
  293. foreach (var network in pluginData.MediatedNetworks)
  294. {
  295. AppLovinPackageManager.UpdateCurrentVersions(network);
  296. }
  297. foreach (var partnerMicroSdk in pluginData.PartnerMicroSdks)
  298. {
  299. AppLovinPackageManager.UpdateCurrentVersions(partnerMicroSdk);
  300. }
  301. return pluginData;
  302. }
  303. /// <summary>
  304. /// Downloads the plugin file for a given network.
  305. /// </summary>
  306. /// <param name="network">Network for which to download the current version.</param>
  307. /// <param name="showImport">Whether or not to show the import window when downloading. Defaults to <c>true</c>.</param>
  308. /// <returns></returns>
  309. public IEnumerator DownloadPlugin(Network network, bool showImport = true)
  310. {
  311. var path = Path.Combine(Application.temporaryCachePath, GetPluginFileName(network)); // TODO: Maybe delete plugin file after finishing import.
  312. var downloadHandler = new DownloadHandlerFile(path);
  313. webRequest = new UnityWebRequest(network.DownloadUrl)
  314. {
  315. method = UnityWebRequest.kHttpVerbGET,
  316. downloadHandler = downloadHandler
  317. };
  318. var operation = webRequest.SendWebRequest();
  319. while (!operation.isDone)
  320. {
  321. yield return new WaitForSeconds(0.1f); // Just wait till webRequest is completed. Our coroutine is pretty rudimentary.
  322. CallDownloadPluginProgressCallback(network.DisplayName, operation.progress, operation.isDone);
  323. }
  324. #if UNITY_2020_1_OR_NEWER
  325. if (webRequest.result != UnityWebRequest.Result.Success)
  326. #else
  327. if (webRequest.isNetworkError || webRequest.isHttpError)
  328. #endif
  329. {
  330. MaxSdkLogger.UserError(webRequest.error);
  331. }
  332. else
  333. {
  334. importingNetwork = network;
  335. AssetDatabase.ImportPackage(path, showImport);
  336. }
  337. webRequest.Dispose();
  338. webRequest = null;
  339. }
  340. /// <summary>
  341. /// Cancels the plugin download if one is in progress.
  342. /// </summary>
  343. public void CancelDownload()
  344. {
  345. if (webRequest == null) return;
  346. webRequest.Abort();
  347. }
  348. /// <summary>
  349. /// Shows a dialog to the user with the given message and logs the error message to console.
  350. /// </summary>
  351. /// <param name="message">The failure message to be shown to the user.</param>
  352. public static void ShowBuildFailureDialog(string message)
  353. {
  354. var openIntegrationManager = EditorUtility.DisplayDialog("AppLovin MAX", message, "Open Integration Manager", "Dismiss");
  355. if (openIntegrationManager)
  356. {
  357. AppLovinIntegrationManagerWindow.ShowManager();
  358. }
  359. MaxSdkLogger.UserError(message);
  360. }
  361. #region Utility Methods
  362. /// <summary>
  363. /// Checks whether or not the given package name is the currently importing package.
  364. /// </summary>
  365. /// <param name="packageName">The name of the package that needs to be checked.</param>
  366. /// <returns>true if the importing package matches the given package name.</returns>
  367. private bool IsImportingNetwork(string packageName)
  368. {
  369. // Note: The pluginName doesn't have the '.unitypackage' extension included in its name but the pluginFileName does. So using Contains instead of Equals.
  370. return importingNetwork != null && GetPluginFileName(importingNetwork).Contains(packageName);
  371. }
  372. private static void CallDownloadPluginProgressCallback(string pluginName, float progress, bool isDone)
  373. {
  374. if (OnDownloadPluginProgressCallback == null) return;
  375. OnDownloadPluginProgressCallback(pluginName, progress, isDone);
  376. }
  377. private static void CallImportPackageCompletedCallback(Network network)
  378. {
  379. if (OnImportPackageCompletedCallback == null) return;
  380. OnImportPackageCompletedCallback(network);
  381. }
  382. private static object GetEditorUserBuildSetting(string name, object defaultValue)
  383. {
  384. var editorUserBuildSettingsType = typeof(EditorUserBuildSettings);
  385. var property = editorUserBuildSettingsType.GetProperty(name);
  386. if (property != null)
  387. {
  388. var value = property.GetValue(null, null);
  389. if (value != null) return value;
  390. }
  391. return defaultValue;
  392. }
  393. private static string GetPluginFileName(Network network)
  394. {
  395. return network.Name.ToLowerInvariant() + "_" + network.LatestVersions.Unity + ".unitypackage";
  396. }
  397. #endregion
  398. }
  399. }