123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- // AppLovin MAX Unity Plugin
- //
- // Created by Santosh Bagadi on 9/3/19.
- // Copyright © 2019 AppLovin. All rights reserved.
- //
- #if UNITY_ANDROID
- using System.Text;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text.RegularExpressions;
- using UnityEditorInternal;
- using UnityEngine;
- using UnityEngine.Networking;
- using Debug = UnityEngine.Debug;
- 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>
- /// 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>
- protected 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,
- #if UNITY_2019_3_OR_NEWER
- false // On Unity 2019.3+, the buildscript closure related lines will to be added to the root build.gradle file.
- #else
- true
- #endif
- );
- // 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>
- protected 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>
- protected 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;
- }
- #if UNITY_2019_3_OR_NEWER
- /// <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>
- protected 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);
- }
- }
- #endif
- private static AppLovinQualityServiceData RetrieveQualityServiceData(string sdkKey)
- {
- var postJson = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey);
- var bodyRaw = Encoding.UTF8.GetBytes(postJson);
- // Upload handler is automatically disposed when UnityWebRequest is disposed
- var uploadHandler = new UploadHandlerRaw(bodyRaw);
- uploadHandler.contentType = "application/json";
- using (var unityWebRequest = new UnityWebRequest("https://api2.safedk.com/v1/build/cred"))
- {
- unityWebRequest.method = UnityWebRequest.kHttpVerbPOST;
- unityWebRequest.uploadHandler = uploadHandler;
- unityWebRequest.downloadHandler = new DownloadHandlerBuffer();
- var operation = unityWebRequest.SendWebRequest();
- // Wait for the download to complete or the request to timeout.
- while (!operation.isDone) { }
- #if UNITY_2020_1_OR_NEWER
- if (unityWebRequest.result != UnityWebRequest.Result.Success)
- #else
- if (unityWebRequest.isNetworkError || unityWebRequest.isHttpError)
- #endif
- {
- MaxSdkLogger.UserError("Failed to retrieve API Key for SDK Key: " + sdkKey + "with error: " + unityWebRequest.error);
- return new AppLovinQualityServiceData();
- }
- try
- {
- return JsonUtility.FromJson<AppLovinQualityServiceData>(unityWebRequest.downloadHandler.text);
- }
- 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)
- {
- var addPlugin = MaxSdkUtils.IsValidString(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>();
- // Check if the plugin exists, if so, update the SDK Key.
- var pluginExists = lines.Any(line => TokenAppLovinPlugin.IsMatch(line));
- if (pluginExists)
- {
- 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);
- }
- }
- }
- // Plugin hasn't been added yet, add it.
- else
- {
- var buildScriptClosureDepth = 0;
- var insideBuildScriptClosure = false;
- var buildScriptMatched = false;
- var qualityServiceRepositoryAdded = false;
- var qualityServiceDependencyClassPathAdded = false;
- var qualityServicePluginAdded = false;
- foreach (var line in lines)
- {
- // Add the line to the output lines.
- outputLines.Add(line);
- // Check if we need to add the build script lines and add them.
- if (addBuildScriptLines)
- {
- 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;
- }
- }
- }
- // Check if we need to add the plugin and add it.
- if (addPlugin)
- {
- // Add the plugin.
- if (!qualityServicePluginAdded && TokenApplicationPlugin.IsMatch(line))
- {
- outputLines.Add(QualityServiceApplyPlugin);
- outputLines.AddRange(GenerateAppLovinPluginClosure(apiKey));
- qualityServicePluginAdded = true;
- }
- }
- }
- if ((addBuildScriptLines && (!qualityServiceRepositoryAdded || !qualityServiceDependencyClassPathAdded)) || (addPlugin && !qualityServicePluginAdded))
- {
- return null;
- }
- }
- return outputLines;
- }
- public static string GetFormattedBuildScriptLine(string buildScriptLine)
- {
- #if UNITY_2022_2_OR_NEWER
- return " "
- #elif UNITY_2019_3_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
|