123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- //
- // 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
- {
- #if UNITY_2019_3_OR_NEWER
- private const string PropertyAndroidX = "android.useAndroidX";
- private const string PropertyJetifier = "android.enableJetifier";
- private const string EnableProperty = "=true";
- #endif
- 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)
- {
- #if UNITY_2019_3_OR_NEWER
- var rootGradleBuildFilePath = Path.Combine(path, "../build.gradle");
- var gradlePropertiesPath = Path.Combine(path, "../gradle.properties");
- var gradleWrapperPropertiesPath = Path.Combine(path, "../gradle/wrapper/gradle-wrapper.properties");
- #else
- var rootGradleBuildFilePath = Path.Combine(path, "build.gradle");
- var gradlePropertiesPath = Path.Combine(path, "gradle.properties");
- var gradleWrapperPropertiesPath = Path.Combine(path, "gradle/wrapper/gradle-wrapper.properties");
- #endif
- 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);
- #if UNITY_2019_3_OR_NEWER
- // 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)));
- #else
- // Add all properties except DexingArtifactTransform since it may already exist. We will re-add it below.
- gradlePropertiesUpdated.AddRange(lines.Where(line => !line.Contains(PropertyDexingArtifactTransform)));
- #endif
- }
- #if UNITY_2019_3_OR_NEWER
- // Enable AndroidX and Jetifier properties
- gradlePropertiesUpdated.Add(PropertyAndroidX + EnableProperty);
- gradlePropertiesUpdated.Add(PropertyJetifier + EnableProperty);
- #endif
- // `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
|