732 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			732 lines
		
	
	
		
			36 KiB
		
	
	
	
		
			C#
		
	
	
	
| //
 | |
| //  MaxIntegrationManager.cs
 | |
| //  AppLovin MAX Unity Plugin
 | |
| //
 | |
| //  Created by Santosh Bagadi on 8/29/19.
 | |
| //  Copyright © 2019 AppLovin. All rights reserved.
 | |
| //
 | |
| 
 | |
| #if UNITY_IOS || UNITY_IPHONE
 | |
| using System;
 | |
| using System.Collections.Generic;
 | |
| using System.Diagnostics;
 | |
| using System.IO;
 | |
| using System.Linq;
 | |
| using System.Text;
 | |
| using System.Text.RegularExpressions;
 | |
| using UnityEditor;
 | |
| using UnityEditor.Callbacks;
 | |
| #if UNITY_2019_3_OR_NEWER
 | |
| using UnityEditor.iOS.Xcode.Extensions;
 | |
| #endif
 | |
| using UnityEditor.iOS.Xcode;
 | |
| using UnityEditor.PackageManager;
 | |
| using UnityEngine;
 | |
| using UnityEngine.Networking;
 | |
| 
 | |
| namespace AppLovinMax.Scripts.IntegrationManager.Editor
 | |
| {
 | |
|     [Serializable]
 | |
|     public class SkAdNetworkData
 | |
|     {
 | |
|         [SerializeField] public string[] SkAdNetworkIds;
 | |
|     }
 | |
| 
 | |
|     public class AppLovinPostProcessiOS
 | |
|     {
 | |
|         private const string OutputFileName = "AppLovinQualityServiceSetup.rb";
 | |
| 
 | |
| #if !UNITY_2019_3_OR_NEWER
 | |
|         private const string UnityMainTargetName = "Unity-iPhone";
 | |
| #endif
 | |
|         // Use a priority of 90 to have AppLovin embed frameworks after Pods are installed (EDM finishes installing Pods at priority 60) and before Firebase Crashlytics runs their scripts (at priority 100).
 | |
|         private const int AppLovinEmbedFrameworksPriority = 90;
 | |
| 
 | |
|         private const string TargetUnityIphonePodfileLine = "target 'Unity-iPhone' do";
 | |
|         private const string UseFrameworksPodfileLine = "use_frameworks!";
 | |
|         private const string UseFrameworksDynamicPodfileLine = "use_frameworks! :linkage => :dynamic";
 | |
|         private const string UseFrameworksStaticPodfileLine = "use_frameworks! :linkage => :static";
 | |
| 
 | |
|         private const string ResourcesDirectoryName = "Resources";
 | |
|         private const string AppLovinMaxResourcesDirectoryName = "AppLovinMAXResources";
 | |
|         private const string AppLovinAdvertisingAttributionEndpoint = "https://postbacks-app.com";
 | |
| 
 | |
|         private const string AppLovinSettingsPlistFileName = "AppLovin-Settings.plist";
 | |
| 
 | |
|         private const string KeySdkKey = "SdkKey";
 | |
| 
 | |
|         private const string AppLovinVerboseLoggingOnKey = "AppLovinVerboseLoggingOn";
 | |
| 
 | |
|         private const string KeyConsentFlowInfo = "ConsentFlowInfo";
 | |
|         private const string KeyConsentFlowEnabled = "ConsentFlowEnabled";
 | |
|         private const string KeyConsentFlowTermsOfService = "ConsentFlowTermsOfService";
 | |
|         private const string KeyConsentFlowPrivacyPolicy = "ConsentFlowPrivacyPolicy";
 | |
|         private const string KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR = "ConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR";
 | |
|         private const string KeyConsentFlowDebugUserGeography = "ConsentFlowDebugUserGeography";
 | |
| 
 | |
|         private const string KeyAppLovinSdkKeyToRemove = "AppLovinSdkKey";
 | |
| 
 | |
|         private static readonly Regex PodfilePodLineRegex = new Regex("pod \'([^\']*)\'");
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Adds AppLovin Quality Service to the iOS project once the project has been exported.
 | |
|         ///
 | |
|         /// 1. Downloads the Quality Service ruby script.
 | |
|         /// 2. Runs the script using Ruby which integrates AppLovin Quality Service to the project.
 | |
|         /// </summary>
 | |
|         [PostProcessBuild(AppLovinPreProcess.CallbackOrder)] // We want to run Quality Service script last.
 | |
|         public static void OnPostProcessBuild(BuildTarget buildTarget, string buildPath)
 | |
|         {
 | |
|             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;
 | |
|             }
 | |
| 
 | |
|             var outputFilePath = Path.Combine(buildPath, OutputFileName);
 | |
| 
 | |
|             // Check if Quality Service is already installed.
 | |
|             if (File.Exists(outputFilePath) && Directory.Exists(Path.Combine(buildPath, "AppLovinQualityService")))
 | |
|             {
 | |
|                 // TODO: Check if there is a way to validate if the SDK key matches the script. Else the pub can't use append when/if they change the SDK Key.
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Download the ruby script needed to install Quality Service
 | |
|             var downloadHandler = new DownloadHandlerFile(outputFilePath);
 | |
|             var postJson = string.Format("{{\"sdk_key\" : \"{0}\"}}", sdkKey);
 | |
|             var bodyRaw = Encoding.UTF8.GetBytes(postJson);
 | |
|             var uploadHandler = new UploadHandlerRaw(bodyRaw);
 | |
|             uploadHandler.contentType = "application/json";
 | |
| 
 | |
|             using (var unityWebRequest = new UnityWebRequest("https://api2.safedk.com/v1/build/ios_setup2"))
 | |
|             {
 | |
|                 unityWebRequest.method = UnityWebRequest.kHttpVerbPOST;
 | |
|                 unityWebRequest.downloadHandler = downloadHandler;
 | |
|                 unityWebRequest.uploadHandler = uploadHandler;
 | |
|                 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("AppLovin Quality Service installation failed. Failed to download script with error: " + unityWebRequest.error);
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Check if Ruby is installed
 | |
|                 var rubyVersion = AppLovinCommandLine.Run("ruby", "--version", buildPath);
 | |
|                 if (rubyVersion.ExitCode != 0)
 | |
|                 {
 | |
|                     MaxSdkLogger.UserError("AppLovin Quality Service installation requires Ruby. Please install Ruby, export it to your system PATH and re-export the project.");
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Ruby is installed, run `ruby AppLovinQualityServiceSetup.rb`
 | |
|                 var result = AppLovinCommandLine.Run("ruby", OutputFileName, buildPath);
 | |
| 
 | |
|                 // Check if we have an error.
 | |
|                 if (result.ExitCode != 0) MaxSdkLogger.UserError("Failed to set up AppLovin Quality Service");
 | |
| 
 | |
|                 MaxSdkLogger.UserDebug(result.Message);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [PostProcessBuild(AppLovinEmbedFrameworksPriority)]
 | |
|         public static void MaxPostProcessPbxProject(BuildTarget buildTarget, string buildPath)
 | |
|         {
 | |
|             var projectPath = PBXProject.GetPBXProjectPath(buildPath);
 | |
|             var project = new PBXProject();
 | |
|             project.ReadFromFile(projectPath);
 | |
| 
 | |
| #if UNITY_2019_3_OR_NEWER
 | |
|             var unityMainTargetGuid = project.GetUnityMainTargetGuid();
 | |
|             var unityFrameworkTargetGuid = project.GetUnityFrameworkTargetGuid();
 | |
| #else
 | |
|             var unityMainTargetGuid = project.TargetGuidByName(UnityMainTargetName);
 | |
|             var unityFrameworkTargetGuid = project.TargetGuidByName(UnityMainTargetName);
 | |
| #endif
 | |
|             EmbedDynamicLibrariesIfNeeded(buildPath, project, unityMainTargetGuid);
 | |
| 
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe, "de", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn, "en", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs, "es", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr, "fr", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa, "ja", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo, "ko", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans, "zh-Hans", buildPath, project, unityMainTargetGuid);
 | |
|             LocalizeUserTrackingDescriptionIfNeeded(AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant, "zh-Hant", buildPath, project, unityMainTargetGuid);
 | |
| 
 | |
|             AddSwiftSupport(buildPath, project, unityFrameworkTargetGuid, unityMainTargetGuid);
 | |
|             AddYandexSettingsIfNeeded(project, unityMainTargetGuid);
 | |
| 
 | |
|             project.WriteToFile(projectPath);
 | |
|         }
 | |
| 
 | |
|         private static void EmbedDynamicLibrariesIfNeeded(string buildPath, PBXProject project, string targetGuid)
 | |
|         {
 | |
|             // Check that the Pods directory exists (it might not if a publisher is building with Generate Podfile setting disabled in EDM).
 | |
|             var podsDirectory = Path.Combine(buildPath, "Pods");
 | |
|             if (!Directory.Exists(podsDirectory) || !ShouldEmbedDynamicLibraries(buildPath)) return;
 | |
| 
 | |
|             var dynamicLibraryPathsToEmbed = GetDynamicLibraryPathsToEmbed(podsDirectory, buildPath);
 | |
|             if (dynamicLibraryPathsToEmbed == null || dynamicLibraryPathsToEmbed.Count == 0) return;
 | |
| 
 | |
| #if UNITY_2019_3_OR_NEWER
 | |
|             foreach (var dynamicLibraryPath in dynamicLibraryPathsToEmbed)
 | |
|             {
 | |
|                 var fileGuid = project.AddFile(dynamicLibraryPath, dynamicLibraryPath);
 | |
|                 project.AddFileToEmbedFrameworks(targetGuid, fileGuid);
 | |
|             }
 | |
| #else
 | |
|             string runpathSearchPaths;
 | |
|             runpathSearchPaths = project.GetBuildPropertyForAnyConfig(targetGuid, "LD_RUNPATH_SEARCH_PATHS");
 | |
|             runpathSearchPaths += string.IsNullOrEmpty(runpathSearchPaths) ? "" : " ";
 | |
| 
 | |
|             // Check if runtime search paths already contains the required search paths for dynamic libraries.
 | |
|             if (runpathSearchPaths.Contains("@executable_path/Frameworks")) return;
 | |
| 
 | |
|             runpathSearchPaths += "@executable_path/Frameworks";
 | |
|             project.SetBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", runpathSearchPaths);
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// |-----------------------------------------------------------------------------------------------------------------------------------------------------|
 | |
|         /// |         embed             |  use_frameworks! (:linkage => :dynamic)  |  use_frameworks! :linkage => :static  |  `use_frameworks!` line not present  |
 | |
|         /// |---------------------------|------------------------------------------|---------------------------------------|--------------------------------------|
 | |
|         /// | Unity-iPhone present      | Do not embed dynamic libraries           | Embed dynamic libraries               | Do not embed dynamic libraries       |
 | |
|         /// | Unity-iPhone not present  | Embed dynamic libraries                  | Embed dynamic libraries               | Embed dynamic libraries              |
 | |
|         /// |-----------------------------------------------------------------------------------------------------------------------------------------------------|
 | |
|         /// </summary>
 | |
|         /// <param name="buildPath">An iOS build path</param>
 | |
|         /// <returns>Whether or not the dynamic libraries should be embedded.</returns>
 | |
|         private static bool ShouldEmbedDynamicLibraries(string buildPath)
 | |
|         {
 | |
|             var podfilePath = Path.Combine(buildPath, "Podfile");
 | |
|             if (!File.Exists(podfilePath)) return false;
 | |
| 
 | |
|             // If the Podfile doesn't have a `Unity-iPhone` target, we should embed the dynamic libraries.
 | |
|             var lines = File.ReadAllLines(podfilePath);
 | |
|             var containsUnityIphoneTarget = lines.Any(line => line.Contains(TargetUnityIphonePodfileLine));
 | |
|             if (!containsUnityIphoneTarget) return true;
 | |
| 
 | |
|             // If the Podfile does not have a `use_frameworks! :linkage => static` line, we should not embed the dynamic libraries.
 | |
|             var useFrameworksStaticLineIndex = Array.FindIndex(lines, line => line.Contains(UseFrameworksStaticPodfileLine));
 | |
|             if (useFrameworksStaticLineIndex == -1) return false;
 | |
| 
 | |
|             // If more than one of the `use_frameworks!` lines are present, CocoaPods will use the last one.
 | |
|             var useFrameworksLineIndex = Array.FindIndex(lines, line => line.Trim() == UseFrameworksPodfileLine); // Check for exact line to avoid matching `use_frameworks! :linkage => static/dynamic`
 | |
|             var useFrameworksDynamicLineIndex = Array.FindIndex(lines, line => line.Contains(UseFrameworksDynamicPodfileLine));
 | |
| 
 | |
|             // Check if `use_frameworks! :linkage => :static` is the last line of the three. If it is, we should embed the dynamic libraries.
 | |
|             return useFrameworksLineIndex < useFrameworksStaticLineIndex && useFrameworksDynamicLineIndex < useFrameworksStaticLineIndex;
 | |
|         }
 | |
| 
 | |
|         private static List<string> GetDynamicLibraryPathsToEmbed(string podsDirectory, string buildPath)
 | |
|         {
 | |
|             var podfilePath = Path.Combine(buildPath, "Podfile");
 | |
|             var dynamicLibraryFrameworksToEmbed = GetDynamicLibraryFrameworksToEmbed(podfilePath);
 | |
| 
 | |
|             return GetDynamicLibraryPathsInProjectToEmbed(podsDirectory, dynamicLibraryFrameworksToEmbed);
 | |
|         }
 | |
| 
 | |
|         private static List<string> GetDynamicLibraryFrameworksToEmbed(string podfilePath)
 | |
|         {
 | |
|             var dynamicLibrariesToEmbed = GetDynamicLibrariesToEmbed();
 | |
| 
 | |
|             var podsInUnityIphoneTarget = GetPodNamesInUnityIphoneTarget(podfilePath);
 | |
|             var dynamicLibrariesToIgnore = dynamicLibrariesToEmbed.Where(dynamicLibraryToEmbed => podsInUnityIphoneTarget.Contains(dynamicLibraryToEmbed.PodName)).ToList();
 | |
| 
 | |
|             // Determine frameworks to embed based on the dynamic libraries to embed and ignore
 | |
|             var dynamicLibraryFrameworksToIgnore = dynamicLibrariesToIgnore.SelectMany(library => library.FrameworkNames).Distinct().ToList();
 | |
|             return dynamicLibrariesToEmbed.SelectMany(library => library.FrameworkNames).Except(dynamicLibraryFrameworksToIgnore).Distinct().ToList();
 | |
|         }
 | |
| 
 | |
|         private static List<DynamicLibraryToEmbed> GetDynamicLibrariesToEmbed()
 | |
|         {
 | |
|             var pluginData = AppLovinIntegrationManager.LoadPluginDataSync();
 | |
|             if (pluginData == null)
 | |
|             {
 | |
|                 MaxSdkLogger.E("Failed to load plugin data. Dynamic libraries will not be embedded.");
 | |
|                 return null;
 | |
|             }
 | |
| 
 | |
|             // Get the dynamic libraries to embed for each network
 | |
|             var librariesToAdd = pluginData.MediatedNetworks
 | |
|                 .Where(network => network.DynamicLibrariesToEmbed != null)
 | |
|                 .SelectMany(network => network.DynamicLibrariesToEmbed
 | |
|                     .Where(libraryToEmbed => IsRequiredNetworkVersionInstalled(libraryToEmbed, network)))
 | |
|                 .ToList();
 | |
| 
 | |
|             // Get the dynamic libraries to embed for AppLovin MAX
 | |
|             if (pluginData.AppLovinMax.DynamicLibrariesToEmbed != null)
 | |
|             {
 | |
|                 librariesToAdd.AddRange(pluginData.AppLovinMax.DynamicLibrariesToEmbed);
 | |
|             }
 | |
| 
 | |
|             // Get the dynamic libraries to embed for third parties
 | |
|             if (pluginData.ThirdPartyDynamicLibrariesToEmbed != null)
 | |
|             {
 | |
|                 // TODO: Add version check for third party dynamic libraries.
 | |
|                 librariesToAdd.AddRange(pluginData.ThirdPartyDynamicLibrariesToEmbed);
 | |
|             }
 | |
| 
 | |
|             return librariesToAdd;
 | |
|         }
 | |
| 
 | |
|         private static List<string> GetPodNamesInUnityIphoneTarget(string podfilePath)
 | |
|         {
 | |
|             var lines = File.ReadAllLines(podfilePath);
 | |
|             var podNamesInUnityIphone = new List<string>();
 | |
| 
 | |
|             var insideUnityIphoneTarget = false;
 | |
|             foreach (var line in lines)
 | |
|             {
 | |
|                 // Loop until we find the `target 'Unity-iPhone'` line
 | |
|                 if (insideUnityIphoneTarget)
 | |
|                 {
 | |
|                     if (line.Trim() == "end") break;
 | |
| 
 | |
|                     if (PodfilePodLineRegex.IsMatch(line))
 | |
|                     {
 | |
|                         var podName = PodfilePodLineRegex.Match(line).Groups[1].Value;
 | |
|                         podNamesInUnityIphone.Add(podName);
 | |
|                     }
 | |
|                 }
 | |
|                 else if (line.Contains(TargetUnityIphonePodfileLine))
 | |
|                 {
 | |
|                     insideUnityIphoneTarget = true;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             return podNamesInUnityIphone;
 | |
|         }
 | |
| 
 | |
|         private static bool IsRequiredNetworkVersionInstalled(DynamicLibraryToEmbed libraryToEmbed, Network network)
 | |
|         {
 | |
|             var currentIosVersion = network.CurrentVersions.Ios;
 | |
|             if (string.IsNullOrEmpty(currentIosVersion)) return false;
 | |
| 
 | |
|             var minIosVersion = libraryToEmbed.MinVersion;
 | |
|             var maxIosVersion = libraryToEmbed.MaxVersion;
 | |
| 
 | |
|             var greaterThanOrEqualToMinVersion = string.IsNullOrEmpty(minIosVersion) || MaxSdkUtils.CompareVersions(currentIosVersion, minIosVersion) != MaxSdkUtils.VersionComparisonResult.Lesser;
 | |
|             var lessThanOrEqualToMaxVersion = string.IsNullOrEmpty(maxIosVersion) || MaxSdkUtils.CompareVersions(currentIosVersion, maxIosVersion) != MaxSdkUtils.VersionComparisonResult.Greater;
 | |
| 
 | |
|             return greaterThanOrEqualToMinVersion && lessThanOrEqualToMaxVersion;
 | |
|         }
 | |
| 
 | |
|         private static List<string> GetDynamicLibraryPathsInProjectToEmbed(string podsDirectory, List<string> dynamicLibrariesToEmbed)
 | |
|         {
 | |
|             var dynamicLibraryPathsPresentInProject = new List<string>();
 | |
|             foreach (var dynamicLibraryToSearch in dynamicLibrariesToEmbed)
 | |
|             {
 | |
|                 // both .framework and .xcframework are directories, not files
 | |
|                 var directories = Directory.GetDirectories(podsDirectory, dynamicLibraryToSearch, SearchOption.AllDirectories);
 | |
|                 if (directories.Length <= 0) continue;
 | |
| 
 | |
|                 var dynamicLibraryAbsolutePath = directories[0];
 | |
|                 var relativePath = GetDynamicLibraryRelativePath(dynamicLibraryAbsolutePath);
 | |
|                 dynamicLibraryPathsPresentInProject.Add(relativePath);
 | |
|             }
 | |
| 
 | |
|             return dynamicLibraryPathsPresentInProject;
 | |
|         }
 | |
| 
 | |
|         private static string GetDynamicLibraryRelativePath(string dynamicLibraryAbsolutePath)
 | |
|         {
 | |
|             var index = dynamicLibraryAbsolutePath.LastIndexOf("Pods", StringComparison.Ordinal);
 | |
|             return dynamicLibraryAbsolutePath.Substring(index);
 | |
|         }
 | |
| 
 | |
|         private static void LocalizeUserTrackingDescriptionIfNeeded(string localizedUserTrackingDescription, string localeCode, string buildPath, PBXProject project, string targetGuid)
 | |
|         {
 | |
|             var resourcesDirectoryPath = Path.Combine(buildPath, AppLovinMaxResourcesDirectoryName);
 | |
|             var localeSpecificDirectoryName = localeCode + ".lproj";
 | |
|             var localeSpecificDirectoryPath = Path.Combine(resourcesDirectoryPath, localeSpecificDirectoryName);
 | |
|             var infoPlistStringsFilePath = Path.Combine(localeSpecificDirectoryPath, "InfoPlist.strings");
 | |
| 
 | |
|             // Check if localization has been disabled between builds, and remove them as needed.
 | |
|             if (ShouldRemoveLocalization(localizedUserTrackingDescription))
 | |
|             {
 | |
|                 if (!File.Exists(infoPlistStringsFilePath)) return;
 | |
| 
 | |
|                 File.Delete(infoPlistStringsFilePath);
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Log an error if we detect a localization file for this language in the `Resources` directory
 | |
|             var legacyResourcedDirectoryPath = Path.Combine(buildPath, ResourcesDirectoryName);
 | |
|             var localeSpecificLegacyDirectoryPath = Path.Combine(legacyResourcedDirectoryPath, localeSpecificDirectoryName);
 | |
|             if (Directory.Exists(localeSpecificLegacyDirectoryPath))
 | |
|             {
 | |
|                 MaxSdkLogger.UserError("Detected existing localization resource for \"" + localeCode + "\" locale. Skipping localization for User Tracking Usage Description. Please disable localization in AppLovin Integration manager and add the localizations to your existing resource.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Create intermediate directories as needed.
 | |
|             if (!Directory.Exists(resourcesDirectoryPath))
 | |
|             {
 | |
|                 Directory.CreateDirectory(resourcesDirectoryPath);
 | |
|             }
 | |
| 
 | |
|             if (!Directory.Exists(localeSpecificDirectoryPath))
 | |
|             {
 | |
|                 Directory.CreateDirectory(localeSpecificDirectoryPath);
 | |
|             }
 | |
| 
 | |
|             var localizedDescriptionLine = "\"NSUserTrackingUsageDescription\" = \"" + localizedUserTrackingDescription + "\";\n";
 | |
|             // File already exists, update it in case the value changed between builds.
 | |
|             if (File.Exists(infoPlistStringsFilePath))
 | |
|             {
 | |
|                 var output = new List<string>();
 | |
|                 var lines = File.ReadAllLines(infoPlistStringsFilePath);
 | |
|                 var keyUpdated = false;
 | |
|                 foreach (var line in lines)
 | |
|                 {
 | |
|                     if (line.Contains("NSUserTrackingUsageDescription"))
 | |
|                     {
 | |
|                         output.Add(localizedDescriptionLine);
 | |
|                         keyUpdated = true;
 | |
|                     }
 | |
|                     else
 | |
|                     {
 | |
|                         output.Add(line);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (!keyUpdated)
 | |
|                 {
 | |
|                     output.Add(localizedDescriptionLine);
 | |
|                 }
 | |
| 
 | |
|                 File.WriteAllText(infoPlistStringsFilePath, string.Join("\n", output.ToArray()) + "\n");
 | |
|             }
 | |
|             // File doesn't exist, create one.
 | |
|             else
 | |
|             {
 | |
|                 File.WriteAllText(infoPlistStringsFilePath, "/* Localized versions of Info.plist keys - Generated by AL MAX plugin */\n" + localizedDescriptionLine);
 | |
|             }
 | |
| 
 | |
|             var localeSpecificDirectoryRelativePath = Path.Combine(AppLovinMaxResourcesDirectoryName, localeSpecificDirectoryName);
 | |
|             var guid = project.AddFolderReference(localeSpecificDirectoryRelativePath, localeSpecificDirectoryRelativePath);
 | |
|             project.AddFileToBuild(targetGuid, guid);
 | |
|         }
 | |
| 
 | |
|         private static bool ShouldRemoveLocalization(string localizedUserTrackingDescription)
 | |
|         {
 | |
|             if (string.IsNullOrEmpty(localizedUserTrackingDescription)) return true;
 | |
| 
 | |
|             var internalSettings = AppLovinInternalSettings.Instance;
 | |
|             return !internalSettings.ConsentFlowEnabled || !internalSettings.UserTrackingUsageLocalizationEnabled;
 | |
|         }
 | |
| 
 | |
|         private static void AddSwiftSupport(string buildPath, PBXProject project, string unityFrameworkTargetGuid, string unityMainTargetGuid)
 | |
|         {
 | |
|             var swiftFileRelativePath = "Classes/MAXSwiftSupport.swift";
 | |
|             var swiftFilePath = Path.Combine(buildPath, swiftFileRelativePath);
 | |
| 
 | |
|             // Add Swift file
 | |
|             CreateSwiftFile(swiftFilePath);
 | |
|             var swiftFileGuid = project.AddFile(swiftFileRelativePath, swiftFileRelativePath);
 | |
|             project.AddFileToBuild(unityFrameworkTargetGuid, swiftFileGuid);
 | |
| 
 | |
|             // Add Swift version property if needed
 | |
|             var swiftVersion = project.GetBuildPropertyForAnyConfig(unityFrameworkTargetGuid, "SWIFT_VERSION");
 | |
|             if (string.IsNullOrEmpty(swiftVersion))
 | |
|             {
 | |
|                 project.SetBuildProperty(unityFrameworkTargetGuid, "SWIFT_VERSION", "5.0");
 | |
|             }
 | |
| 
 | |
|             // Some publishers may configure these settings in their own post-processing scripts.
 | |
|             // Only set them if they haven't already been defined to avoid overwriting publisher-defined values.
 | |
|             var enableModules = project.GetBuildPropertyForAnyConfig(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES");
 | |
|             if (string.IsNullOrEmpty(enableModules))
 | |
|             {
 | |
|                 project.SetBuildProperty(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES", "YES");
 | |
|             }
 | |
| 
 | |
|             var alwaysEmbedSwiftLibraries = project.GetBuildPropertyForAnyConfig(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES");
 | |
|             if (string.IsNullOrEmpty(alwaysEmbedSwiftLibraries))
 | |
|             {
 | |
|                 project.SetBuildProperty(unityMainTargetGuid, "ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES", "YES");
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static void CreateSwiftFile(string swiftFilePath)
 | |
|         {
 | |
|             if (File.Exists(swiftFilePath)) return;
 | |
| 
 | |
|             // Create a file to write to.
 | |
|             using (var writer = File.CreateText(swiftFilePath))
 | |
|             {
 | |
|                 writer.WriteLine("//\n//  MAXSwiftSupport.swift\n//");
 | |
|                 writer.WriteLine("\nimport Foundation\n");
 | |
|                 writer.WriteLine("// This file ensures the project includes Swift support.");
 | |
|                 writer.WriteLine("// It is automatically generated by the MAX Unity Plugin.");
 | |
|                 writer.Close();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [PostProcessBuild(AppLovinPreProcess.CallbackOrder)]
 | |
|         public static void MaxPostProcessPlist(BuildTarget buildTarget, string path)
 | |
|         {
 | |
|             var plistPath = Path.Combine(path, "Info.plist");
 | |
|             var plist = new PlistDocument();
 | |
|             plist.ReadFromFile(plistPath);
 | |
| 
 | |
|             RemoveAttributionReportEndpointIfNeeded(plist);
 | |
| 
 | |
|             EnableVerboseLoggingIfNeeded(plist);
 | |
|             AddGoogleApplicationIdIfNeeded(plist);
 | |
| 
 | |
|             AddSdkSettings(plist, path);
 | |
|             AddSkAdNetworksInfoIfNeeded(plist);
 | |
|             RemoveSdkKeyIfNeeded(plist);
 | |
| 
 | |
|             plist.WriteToFile(plistPath);
 | |
|         }
 | |
| 
 | |
|         private static void RemoveAttributionReportEndpointIfNeeded(PlistDocument plist)
 | |
|         {
 | |
|             PlistElement attributionReportEndPoint;
 | |
|             plist.root.values.TryGetValue("NSAdvertisingAttributionReportEndpoint", out attributionReportEndPoint);
 | |
| 
 | |
|             // We no longer support this feature. Check if we had previously set the attribution endpoint and un-set it.
 | |
|             if (attributionReportEndPoint == null || !AppLovinAdvertisingAttributionEndpoint.Equals(attributionReportEndPoint.AsString())) return;
 | |
| 
 | |
|             MaxSdkLogger.UserWarning("Global SKAdNetwork postback forwarding is no longer supported by AppLovin. Removing AppLovin Advertising Attribution Endpoint from Info.plist.");
 | |
|             plist.root.values.Remove("NSAdvertisingAttributionReportEndpoint");
 | |
|         }
 | |
| 
 | |
|         private static void EnableVerboseLoggingIfNeeded(PlistDocument plist)
 | |
|         {
 | |
|             if (!EditorPrefs.HasKey(MaxSdkLogger.KeyVerboseLoggingEnabled)) return;
 | |
| 
 | |
|             var enabled = EditorPrefs.GetBool(MaxSdkLogger.KeyVerboseLoggingEnabled);
 | |
|             if (enabled)
 | |
|             {
 | |
|                 plist.root.SetBoolean(AppLovinVerboseLoggingOnKey, true);
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 plist.root.values.Remove(AppLovinVerboseLoggingOnKey);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static void AddGoogleApplicationIdIfNeeded(PlistDocument plist)
 | |
|         {
 | |
|             if (!AppLovinPackageManager.IsAdapterInstalled("Google") && !AppLovinPackageManager.IsAdapterInstalled("GoogleAdManager")) return;
 | |
| 
 | |
|             const string googleApplicationIdentifier = "GADApplicationIdentifier";
 | |
|             var appId = AppLovinSettings.Instance.AdMobIosAppId;
 | |
|             // Log error if the App ID is not set.
 | |
|             if (string.IsNullOrEmpty(appId) || !appId.StartsWith("ca-app-pub-"))
 | |
|             {
 | |
|                 MaxSdkLogger.UserError("[AppLovin MAX] Google App ID is not set. Please enter a valid app ID within the AppLovin Integration Manager window.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             plist.root.SetString(googleApplicationIdentifier, appId);
 | |
|         }
 | |
| 
 | |
|         private static void AddYandexSettingsIfNeeded(PBXProject project, string unityMainTargetGuid)
 | |
|         {
 | |
|             if (!AppLovinPackageManager.IsAdapterInstalled("Yandex")) return;
 | |
| 
 | |
|             if (MaxSdkUtils.CompareVersions(PlayerSettings.iOS.targetOSVersionString, "12.0") == MaxSdkUtils.VersionComparisonResult.Lesser)
 | |
|             {
 | |
|                 MaxSdkLogger.UserWarning("Your iOS target version is under the minimum required version by Yandex. Please update it to 12.0 or newer in your ProjectSettings and rebuild your project.");
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             project.SetBuildProperty(unityMainTargetGuid, "GENERATE_INFOPLIST_FILE", "NO");
 | |
|         }
 | |
| 
 | |
|         private static void AddSdkSettings(PlistDocument infoPlist, string buildPath)
 | |
|         {
 | |
|             var sdkSettingsPlistPath = Path.Combine(buildPath, AppLovinSettingsPlistFileName);
 | |
|             var sdkSettingsPlist = new PlistDocument();
 | |
|             if (File.Exists(sdkSettingsPlistPath))
 | |
|             {
 | |
|                 sdkSettingsPlist.ReadFromFile(sdkSettingsPlistPath);
 | |
|             }
 | |
| 
 | |
|             // Add the SDK key to the SDK settings plist.
 | |
|             sdkSettingsPlist.root.SetString(KeySdkKey, AppLovinSettings.Instance.SdkKey);
 | |
| 
 | |
|             // Add consent flow settings if needed.
 | |
|             EnableConsentFlowIfNeeded(sdkSettingsPlist, infoPlist);
 | |
| 
 | |
|             sdkSettingsPlist.WriteToFile(sdkSettingsPlistPath);
 | |
| 
 | |
|             var projectPath = PBXProject.GetPBXProjectPath(buildPath);
 | |
|             var project = new PBXProject();
 | |
|             project.ReadFromFile(projectPath);
 | |
| 
 | |
| #if UNITY_2019_3_OR_NEWER
 | |
|             var unityMainTargetGuid = project.GetUnityMainTargetGuid();
 | |
| #else
 | |
|             var unityMainTargetGuid = project.TargetGuidByName(UnityMainTargetName);
 | |
| #endif
 | |
| 
 | |
|             var guid = project.AddFile(AppLovinSettingsPlistFileName, AppLovinSettingsPlistFileName);
 | |
|             project.AddFileToBuild(unityMainTargetGuid, guid);
 | |
|             project.WriteToFile(projectPath);
 | |
|         }
 | |
| 
 | |
|         private static void EnableConsentFlowIfNeeded(PlistDocument applovinSettingsPlist, PlistDocument infoPlist)
 | |
|         {
 | |
|             var consentFlowEnabled = AppLovinInternalSettings.Instance.ConsentFlowEnabled;
 | |
|             if (!consentFlowEnabled) return;
 | |
| 
 | |
|             var userTrackingUsageDescription = AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn;
 | |
|             var privacyPolicyUrl = AppLovinInternalSettings.Instance.ConsentFlowPrivacyPolicyUrl;
 | |
|             if (string.IsNullOrEmpty(userTrackingUsageDescription) || string.IsNullOrEmpty(privacyPolicyUrl))
 | |
|             {
 | |
|                 AppLovinIntegrationManager.ShowBuildFailureDialog("You cannot use the AppLovin SDK's consent flow without defining a Privacy Policy URL and the `User Tracking Usage Description` in the AppLovin Integration Manager. \n\n" +
 | |
|                                                                   "Both values must be included to enable the SDK's consent flow.");
 | |
| 
 | |
|                 // No need to update the info.plist here. Default consent flow state will be determined on the SDK side.
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             var consentFlowInfoRoot = applovinSettingsPlist.root.CreateDict(KeyConsentFlowInfo);
 | |
|             consentFlowInfoRoot.SetBoolean(KeyConsentFlowEnabled, consentFlowEnabled);
 | |
|             consentFlowInfoRoot.SetString(KeyConsentFlowPrivacyPolicy, privacyPolicyUrl);
 | |
| 
 | |
|             var termsOfServiceUrl = AppLovinInternalSettings.Instance.ConsentFlowTermsOfServiceUrl;
 | |
|             if (MaxSdkUtils.IsValidString(termsOfServiceUrl))
 | |
|             {
 | |
|                 consentFlowInfoRoot.SetString(KeyConsentFlowTermsOfService, termsOfServiceUrl);
 | |
|             }
 | |
| 
 | |
|             var shouldShowTermsAndPrivacyPolicyAlertInGdpr = AppLovinInternalSettings.Instance.ShouldShowTermsAndPrivacyPolicyAlertInGDPR;
 | |
|             consentFlowInfoRoot.SetBoolean(KeyConsentFlowShowTermsAndPrivacyPolicyAlertInGDPR, shouldShowTermsAndPrivacyPolicyAlertInGdpr);
 | |
| 
 | |
|             var debugUserGeography = AppLovinInternalSettings.Instance.DebugUserGeography;
 | |
|             if (debugUserGeography == MaxSdkBase.ConsentFlowUserGeography.Gdpr)
 | |
|             {
 | |
|                 consentFlowInfoRoot.SetString(KeyConsentFlowDebugUserGeography, "gdpr");
 | |
|             }
 | |
| 
 | |
|             infoPlist.root.SetString("NSUserTrackingUsageDescription", userTrackingUsageDescription);
 | |
|         }
 | |
| 
 | |
|         private static void AddSkAdNetworksInfoIfNeeded(PlistDocument plist)
 | |
|         {
 | |
|             var skAdNetworkData = GetSkAdNetworkData();
 | |
|             var skAdNetworkIds = skAdNetworkData.SkAdNetworkIds;
 | |
|             // Check if we have a valid list of SKAdNetworkIds that need to be added.
 | |
|             if (skAdNetworkIds == null || skAdNetworkIds.Length < 1) return;
 | |
| 
 | |
|             //
 | |
|             // Add the SKAdNetworkItems to the plist. It should look like following:
 | |
|             //
 | |
|             //    <key>SKAdNetworkItems</key>
 | |
|             //    <array>
 | |
|             //        <dict>
 | |
|             //            <key>SKAdNetworkIdentifier</key>
 | |
|             //            <string>ABC123XYZ.skadnetwork</string>
 | |
|             //        </dict>
 | |
|             //        <dict>
 | |
|             //            <key>SKAdNetworkIdentifier</key>
 | |
|             //            <string>123QWE456.skadnetwork</string>
 | |
|             //        </dict>
 | |
|             //        <dict>
 | |
|             //            <key>SKAdNetworkIdentifier</key>
 | |
|             //            <string>987XYZ123.skadnetwork</string>
 | |
|             //        </dict>
 | |
|             //    </array>
 | |
|             //
 | |
|             PlistElement skAdNetworkItems;
 | |
|             plist.root.values.TryGetValue("SKAdNetworkItems", out skAdNetworkItems);
 | |
|             var existingSkAdNetworkIds = new HashSet<string>();
 | |
|             // Check if SKAdNetworkItems array is already in the Plist document and collect all the IDs that are already present.
 | |
|             if (skAdNetworkItems != null && skAdNetworkItems.GetType() == typeof(PlistElementArray))
 | |
|             {
 | |
|                 var plistElementDictionaries = skAdNetworkItems.AsArray().values.Where(plistElement => plistElement.GetType() == typeof(PlistElementDict));
 | |
|                 foreach (var plistElement in plistElementDictionaries)
 | |
|                 {
 | |
|                     PlistElement existingId;
 | |
|                     plistElement.AsDict().values.TryGetValue("SKAdNetworkIdentifier", out existingId);
 | |
|                     if (existingId == null || existingId.GetType() != typeof(PlistElementString) || string.IsNullOrEmpty(existingId.AsString())) continue;
 | |
| 
 | |
|                     existingSkAdNetworkIds.Add(existingId.AsString());
 | |
|                 }
 | |
|             }
 | |
|             // Else, create an array of SKAdNetworkItems into which we will add our IDs.
 | |
|             else
 | |
|             {
 | |
|                 skAdNetworkItems = plist.root.CreateArray("SKAdNetworkItems");
 | |
|             }
 | |
| 
 | |
|             foreach (var skAdNetworkId in skAdNetworkIds)
 | |
|             {
 | |
|                 // Skip adding IDs that are already in the array.
 | |
|                 if (existingSkAdNetworkIds.Contains(skAdNetworkId)) continue;
 | |
| 
 | |
|                 var skAdNetworkItemDict = skAdNetworkItems.AsArray().AddDict();
 | |
|                 skAdNetworkItemDict.SetString("SKAdNetworkIdentifier", skAdNetworkId);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static SkAdNetworkData GetSkAdNetworkData()
 | |
|         {
 | |
|             // Get the list of installed ad networks to be passed up
 | |
|             var installedNetworks = AppLovinPackageManager.GetInstalledMediationNetworks();
 | |
|             var uriBuilder = new UriBuilder("https://unity.applovin.com/max/1.0/skadnetwork_ids");
 | |
|             var adNetworks = string.Join(",", installedNetworks.ToArray());
 | |
|             if (MaxSdkUtils.IsValidString(adNetworks))
 | |
|             {
 | |
|                 uriBuilder.Query += string.Format("ad_networks={0}", adNetworks);
 | |
|             }
 | |
| 
 | |
|             using (var unityWebRequest = UnityWebRequest.Get(uriBuilder.ToString()))
 | |
|             {
 | |
|                 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 SKAdNetwork IDs with error: " + unityWebRequest.error);
 | |
|                     return new SkAdNetworkData();
 | |
|                 }
 | |
| 
 | |
|                 try
 | |
|                 {
 | |
|                     return JsonUtility.FromJson<SkAdNetworkData>(unityWebRequest.downloadHandler.text);
 | |
|                 }
 | |
|                 catch (Exception exception)
 | |
|                 {
 | |
|                     MaxSdkLogger.UserError("Failed to parse data '" + unityWebRequest.downloadHandler.text + "' with exception: " + exception);
 | |
|                     return new SkAdNetworkData();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static void RemoveSdkKeyIfNeeded(PlistDocument plist)
 | |
|         {
 | |
|             if (!plist.root.values.ContainsKey(KeyAppLovinSdkKeyToRemove)) return;
 | |
| 
 | |
|             plist.root.values.Remove(KeyAppLovinSdkKeyToRemove);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| #endif
 |