AppLovinIntegrationManager.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. var index = adapterVersion.LastIndexOf(".", StringComparison.Ordinal);
  105. return index > 0 ? adapterVersion.Substring(0, index) : adapterVersion;
  106. }
  107. }
  108. /// <summary>
  109. /// A manager class for MAX integration manager window.
  110. /// </summary>
  111. public class AppLovinIntegrationManager
  112. {
  113. /// <summary>
  114. /// Delegate to be called when downloading a plugin with the progress percentage.
  115. /// </summary>
  116. /// <param name="pluginName">The name of the plugin being downloaded.</param>
  117. /// <param name="progress">Percentage downloaded.</param>
  118. /// <param name="done">Whether or not the download is complete.</param>
  119. public delegate void DownloadPluginProgressCallback(string pluginName, float progress, bool done);
  120. /// <summary>
  121. /// Delegate to be called when a plugin package is imported.
  122. /// </summary>
  123. /// <param name="network">The network data for which the package is imported.</param>
  124. public delegate void ImportPackageCompletedCallback(Network network);
  125. private static readonly AppLovinIntegrationManager instance = new AppLovinIntegrationManager();
  126. internal static readonly string GradleTemplatePath = Path.Combine("Assets/Plugins/Android", "mainTemplate.gradle");
  127. private const string MaxSdkAssetExportPath = "MaxSdk/Scripts/MaxSdk.cs";
  128. private const string MaxSdkMediationExportPath = "MaxSdk/Mediation";
  129. private static readonly string PluginDataEndpoint = "https://unity.applovin.com/max/1.0/integration_manager_info?plugin_version={0}";
  130. private static string _externalDependencyManagerVersion;
  131. public static DownloadPluginProgressCallback OnDownloadPluginProgressCallback;
  132. public static ImportPackageCompletedCallback OnImportPackageCompletedCallback;
  133. private UnityWebRequest webRequest;
  134. private Network importingNetwork;
  135. /// <summary>
  136. /// An Instance of the Integration manager.
  137. /// </summary>
  138. public static AppLovinIntegrationManager Instance
  139. {
  140. get { return instance; }
  141. }
  142. /// <summary>
  143. /// The parent directory path where the MaxSdk plugin directory is placed.
  144. /// </summary>
  145. public static string PluginParentDirectory
  146. {
  147. get
  148. {
  149. // Search for the asset with the default exported path first, In most cases, we should be able to find the asset.
  150. // 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).
  151. var maxSdkScriptAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkAssetExportPath);
  152. // maxSdkScriptAssetPath will always have AltDirectorySeparatorChar (/) as the path separator. Convert to platform specific path.
  153. return maxSdkScriptAssetPath.Replace(MaxSdkAssetExportPath, "")
  154. .Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
  155. }
  156. }
  157. public static string MediationDirectory
  158. {
  159. get
  160. {
  161. var mediationAssetPath = MaxSdkUtils.GetAssetPathForExportPath(MaxSdkMediationExportPath);
  162. return mediationAssetPath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
  163. }
  164. }
  165. /// <summary>
  166. /// Whether or not the plugin is in the Unity Package Manager.
  167. /// </summary>
  168. public static bool IsPluginInPackageManager
  169. {
  170. get { return PluginParentDirectory.StartsWith("Packages"); }
  171. }
  172. /// <summary>
  173. /// Whether or not gradle build system is enabled.
  174. /// </summary>
  175. public static bool GradleBuildEnabled
  176. {
  177. get { return GetEditorUserBuildSetting("androidBuildSystem", "").ToString().Equals("Gradle"); }
  178. }
  179. /// <summary>
  180. /// Whether or not Gradle template is enabled.
  181. /// </summary>
  182. public static bool GradleTemplateEnabled
  183. {
  184. get { return GradleBuildEnabled && File.Exists(GradleTemplatePath); }
  185. }
  186. /// <summary>
  187. /// Whether or not the Quality Service settings can be processed which requires Gradle template enabled or Unity IDE newer than version 2018_2.
  188. /// </summary>
  189. public static bool CanProcessAndroidQualityServiceSettings
  190. {
  191. get { return GradleTemplateEnabled || GradleBuildEnabled; }
  192. }
  193. /// <summary>
  194. /// The External Dependency Manager version obtained dynamically.
  195. /// </summary>
  196. public static string ExternalDependencyManagerVersion
  197. {
  198. get
  199. {
  200. if (MaxSdkUtils.IsValidString(_externalDependencyManagerVersion)) return _externalDependencyManagerVersion;
  201. try
  202. {
  203. var versionHandlerVersionNumberType = Type.GetType("Google.VersionHandlerVersionNumber, Google.VersionHandlerImpl");
  204. _externalDependencyManagerVersion = versionHandlerVersionNumberType.GetProperty("Value").GetValue(null, null).ToString();
  205. }
  206. #pragma warning disable 0168
  207. catch (Exception ignored)
  208. #pragma warning restore 0168
  209. {
  210. _externalDependencyManagerVersion = "Failed to get version.";
  211. }
  212. return _externalDependencyManagerVersion;
  213. }
  214. }
  215. private AppLovinIntegrationManager()
  216. {
  217. // Add asset import callbacks.
  218. AssetDatabase.importPackageCompleted += packageName =>
  219. {
  220. if (!IsImportingNetwork(packageName)) return;
  221. AssetDatabase.Refresh();
  222. CallImportPackageCompletedCallback(importingNetwork);
  223. importingNetwork = null;
  224. };
  225. AssetDatabase.importPackageCancelled += packageName =>
  226. {
  227. if (!IsImportingNetwork(packageName)) return;
  228. MaxSdkLogger.UserDebug("Package import cancelled.");
  229. importingNetwork = null;
  230. };
  231. AssetDatabase.importPackageFailed += (packageName, errorMessage) =>
  232. {
  233. if (!IsImportingNetwork(packageName)) return;
  234. MaxSdkLogger.UserError(errorMessage);
  235. importingNetwork = null;
  236. };
  237. }
  238. static AppLovinIntegrationManager() { }
  239. public static PluginData LoadPluginDataSync()
  240. {
  241. var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
  242. using (var unityWebRequest = UnityWebRequest.Get(url))
  243. {
  244. var operation = unityWebRequest.SendWebRequest();
  245. // Just wait till www is done
  246. while (!operation.isDone) { }
  247. return CreatePluginDataFromWebResponse(unityWebRequest);
  248. }
  249. }
  250. /// <summary>
  251. /// Loads the plugin data to be display by integration manager window.
  252. /// </summary>
  253. /// <param name="callback">Callback to be called once the plugin data download completes.</param>
  254. public IEnumerator LoadPluginData(Action<PluginData> callback)
  255. {
  256. var url = string.Format(PluginDataEndpoint, MaxSdk.Version);
  257. using (var unityWebRequest = UnityWebRequest.Get(url))
  258. {
  259. var operation = unityWebRequest.SendWebRequest();
  260. while (!operation.isDone) yield return new WaitForSeconds(0.1f); // Just wait till www is done. Our coroutine is pretty rudimentary.
  261. var pluginData = CreatePluginDataFromWebResponse(unityWebRequest);
  262. callback(pluginData);
  263. }
  264. }
  265. private static PluginData CreatePluginDataFromWebResponse(UnityWebRequest unityWebRequest)
  266. {
  267. #if UNITY_2020_1_OR_NEWER
  268. if (unityWebRequest.result != UnityWebRequest.Result.Success)
  269. #else
  270. if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
  271. #endif
  272. {
  273. MaxSdkLogger.E("Failed to load plugin data. Please check your internet connection.");
  274. return null;
  275. }
  276. PluginData pluginData;
  277. try
  278. {
  279. pluginData = JsonUtility.FromJson<PluginData>(unityWebRequest.downloadHandler.text);
  280. AppLovinPackageManager.PluginData = pluginData;
  281. }
  282. catch (Exception exception)
  283. {
  284. Console.WriteLine(exception);
  285. pluginData = null;
  286. }
  287. if (pluginData == null) return null;
  288. // Get current version of the plugin
  289. var appLovinMax = pluginData.AppLovinMax;
  290. AppLovinPackageManager.UpdateCurrentVersions(appLovinMax);
  291. // Get current versions for all the mediation networks.
  292. foreach (var network in pluginData.MediatedNetworks)
  293. {
  294. AppLovinPackageManager.UpdateCurrentVersions(network);
  295. }
  296. foreach (var partnerMicroSdk in pluginData.PartnerMicroSdks)
  297. {
  298. AppLovinPackageManager.UpdateCurrentVersions(partnerMicroSdk);
  299. }
  300. return pluginData;
  301. }
  302. /// <summary>
  303. /// Downloads the plugin file for a given network.
  304. /// </summary>
  305. /// <param name="network">Network for which to download the current version.</param>
  306. /// <param name="showImport">Whether or not to show the import window when downloading. Defaults to <c>true</c>.</param>
  307. /// <returns></returns>
  308. public IEnumerator DownloadPlugin(Network network, bool showImport = true)
  309. {
  310. var path = Path.Combine(Application.temporaryCachePath, GetPluginFileName(network)); // TODO: Maybe delete plugin file after finishing import.
  311. var downloadHandler = new DownloadHandlerFile(path);
  312. webRequest = new UnityWebRequest(network.DownloadUrl)
  313. {
  314. method = UnityWebRequest.kHttpVerbGET,
  315. downloadHandler = downloadHandler
  316. };
  317. var operation = webRequest.SendWebRequest();
  318. while (!operation.isDone)
  319. {
  320. yield return new WaitForSeconds(0.1f); // Just wait till webRequest is completed. Our coroutine is pretty rudimentary.
  321. CallDownloadPluginProgressCallback(network.DisplayName, operation.progress, operation.isDone);
  322. }
  323. #if UNITY_2020_1_OR_NEWER
  324. if (webRequest.result != UnityWebRequest.Result.Success)
  325. #else
  326. if (webRequest.isNetworkError || webRequest.isHttpError)
  327. #endif
  328. {
  329. MaxSdkLogger.UserError(webRequest.error);
  330. }
  331. else
  332. {
  333. importingNetwork = network;
  334. AssetDatabase.ImportPackage(path, showImport);
  335. }
  336. webRequest.Dispose();
  337. webRequest = null;
  338. }
  339. /// <summary>
  340. /// Cancels the plugin download if one is in progress.
  341. /// </summary>
  342. public void CancelDownload()
  343. {
  344. if (webRequest == null) return;
  345. webRequest.Abort();
  346. }
  347. /// <summary>
  348. /// Shows a dialog to the user with the given message and logs the error message to console.
  349. /// </summary>
  350. /// <param name="message">The failure message to be shown to the user.</param>
  351. public static void ShowBuildFailureDialog(string message)
  352. {
  353. var openIntegrationManager = EditorUtility.DisplayDialog("AppLovin MAX", message, "Open Integration Manager", "Dismiss");
  354. if (openIntegrationManager)
  355. {
  356. AppLovinIntegrationManagerWindow.ShowManager();
  357. }
  358. MaxSdkLogger.UserError(message);
  359. }
  360. #region Utility Methods
  361. /// <summary>
  362. /// Checks whether or not the given package name is the currently importing package.
  363. /// </summary>
  364. /// <param name="packageName">The name of the package that needs to be checked.</param>
  365. /// <returns>true if the importing package matches the given package name.</returns>
  366. private bool IsImportingNetwork(string packageName)
  367. {
  368. // Note: The pluginName doesn't have the '.unitypackage' extension included in its name but the pluginFileName does. So using Contains instead of Equals.
  369. return importingNetwork != null && GetPluginFileName(importingNetwork).Contains(packageName);
  370. }
  371. private static void CallDownloadPluginProgressCallback(string pluginName, float progress, bool isDone)
  372. {
  373. if (OnDownloadPluginProgressCallback == null) return;
  374. OnDownloadPluginProgressCallback(pluginName, progress, isDone);
  375. }
  376. private static void CallImportPackageCompletedCallback(Network network)
  377. {
  378. if (OnImportPackageCompletedCallback == null) return;
  379. OnImportPackageCompletedCallback(network);
  380. }
  381. private static object GetEditorUserBuildSetting(string name, object defaultValue)
  382. {
  383. var editorUserBuildSettingsType = typeof(EditorUserBuildSettings);
  384. var property = editorUserBuildSettingsType.GetProperty(name);
  385. if (property != null)
  386. {
  387. var value = property.GetValue(null, null);
  388. if (value != null) return value;
  389. }
  390. return defaultValue;
  391. }
  392. private static string GetPluginFileName(Network network)
  393. {
  394. return network.Name.ToLowerInvariant() + "_" + network.LatestVersions.Unity + ".unitypackage";
  395. }
  396. #endregion
  397. }
  398. }