2025-08-30 04:55:22 +00:00
// 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 ) )
{
2025-09-28 02:03:20 +00:00
//MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. SDK Key is empty. Please enter the AppLovin SDK Key in the Integration Manager.");
2025-08-30 04:55:22 +00:00
return ;
}
// Retrieve the API Key using the SDK Key.
var qualityServiceData = RetrieveQualityServiceData ( sdkKey ) ;
var apiKey = qualityServiceData . api_key ;
if ( string . IsNullOrEmpty ( apiKey ) )
{
2025-09-28 02:03:20 +00:00
//MaxSdkLogger.UserError("Failed to install AppLovin Quality Service plugin. API Key is empty.");
2025-08-30 04:55:22 +00:00
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