You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/MaxSdk/Scripts/IntegrationManager/Editor/AppLovinPostProcessiOS.cs

739 lines
37 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.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Callbacks;
#if UNITY_2019_3_OR_NEWER
using UnityEditor.iOS.Xcode.Extensions;
#endif
using UnityEditor.iOS.Xcode;
using UnityEngine;
using Debug = UnityEngine.Debug;
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 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";
#else
private const string UnityMainTargetName = "Unity-iPhone";
#endif
private const string LegacyResourcesDirectoryName = "Resources";
private const string AppLovinMaxResourcesDirectoryName = "AppLovinMAXResources";
private const string AppLovinAdvertisingAttributionEndpoint = "https://postbacks-app.com";
private const string AppLovinSettingsPlistFileName = "AppLovin-Settings.plist";
private const string KeyConsentFlowInfo = "ConsentFlowInfo";
private const string KeyConsentFlowEnabled = "ConsentFlowEnabled";
private const string KeyConsentFlowTermsOfService = "ConsentFlowTermsOfService";
private const string KeyConsentFlowPrivacyPolicy = "ConsentFlowPrivacyPolicy";
private const string KeyConsentFlowDebugUserGeography = "ConsentFlowDebugUserGeography";
private static List<string> DynamicLibrariesToEmbed
{
get
{
var dynamicLibrariesToEmbed = new List<string>
{
"AppLovinSDK.xcframework",
"DTBiOSSDK.xcframework",
"FBAEMKit.xcframework",
"FBSDKCoreKit_Basics.xcframework",
"FBSDKCoreKit.xcframework",
"FBSDKGamingServicesKit.xcframework",
"FBSDKLoginKit.xcframework",
"FBSDKShareKit.xcframework",
"HyprMX.xcframework",
"Maio.xcframework",
"MobileFuseSDK.xcframework",
"MolocoSDK.xcframework",
"OMSDK_Appodeal.xcframework",
"OMSDK_Ogury.xcframework",
"OMSDK_Pubnativenet.xcframework",
"OMSDK_Smaato.xcframework"
};
// LinkedIn Audience Network SDK is distributed as a static library starting version 1.2.0
if (AppLovinIntegrationManager.IsAdapterOlderThanMinVersionInstalled("LinkedIn", "1.2.0.0"))
{
dynamicLibrariesToEmbed.Add("LinkedinAudienceNetwork.xcframework");
}
// Fyber/IA SDK is distributed as a static library starting version 8.2.7
if (AppLovinIntegrationManager.IsAdapterOlderThanMinVersionInstalled("Fyber", "8.2.7.0"))
{
dynamicLibrariesToEmbed.Add("IASDKCore.xcframework");
}
if (AppLovinIntegrationManager.IsAdapterInstalled("InMobi", "10.7.2.0"))
{
dynamicLibrariesToEmbed.Add("InMobiSDK.xcframework");
}
if (AppLovinIntegrationManager.IsAdapterInstalled("Smaato", "22.8.3.0"))
{
dynamicLibrariesToEmbed.AddRange(new List<string>()
{
"SmaatoSDKBanner.xcframework",
"SmaatoSDKCore.xcframework",
"SmaatoSDKInAppBidding.xcframework",
"SmaatoSDKInterstitial.xcframework",
"SmaatoSDKNative.xcframework",
"SmaatoSDKOpenMeasurement.xcframework",
"SmaatoSDKOutstream.xcframework",
"SmaatoSDKRewardedAds.xcframework",
"SmaatoSDKRichMedia.xcframework",
"SmaatoSDKVideo.xcframework"
});
}
if (AppLovinIntegrationManager.IsAdapterInstalled("Verve", "3.0.0.0"))
{
dynamicLibrariesToEmbed.Add("ATOM.xcframework");
}
return dynamicLibrariesToEmbed;
}
}
private static string PluginMediationDirectory
{
get
{
var pluginParentDir = AppLovinIntegrationManager.MediationSpecificPluginParentDirectory;
return Path.Combine(pluginParentDir, "MaxSdk/Mediation/");
}
}
/// <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(int.MaxValue)] // 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);
}
}
[PostProcessBuildAttribute(int.MaxValue)]
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);
var internalSettingsEnabled = AppLovinInternalSettings.Instance.ConsentFlowEnabled;
var userTrackingUsageDescriptionDe = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionDe : AppLovinSettings.Instance.UserTrackingUsageDescriptionDe;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionDe, "de", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionEn = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEn : AppLovinSettings.Instance.UserTrackingUsageDescriptionEn;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionEn, "en", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionEs = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionEs : AppLovinSettings.Instance.UserTrackingUsageDescriptionEs;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionEs, "es", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionFr = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionFr : AppLovinSettings.Instance.UserTrackingUsageDescriptionFr;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionFr, "fr", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionJa = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionJa : AppLovinSettings.Instance.UserTrackingUsageDescriptionJa;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionJa, "ja", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionKo = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionKo : AppLovinSettings.Instance.UserTrackingUsageDescriptionKo;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionKo, "ko", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionZhHans = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHans : AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHans;
LocalizeUserTrackingDescriptionIfNeeded(userTrackingUsageDescriptionZhHans, "zh-Hans", buildPath, project, unityMainTargetGuid);
var userTrackingUsageDescriptionZhHant = internalSettingsEnabled ? AppLovinInternalSettings.Instance.UserTrackingUsageDescriptionZhHant : AppLovinSettings.Instance.UserTrackingUsageDescriptionZhHant;
LocalizeUserTrackingDescriptionIfNeeded(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)) return;
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 index = dynamicLibraryAbsolutePath.LastIndexOf("Pods");
var relativePath = dynamicLibraryAbsolutePath.Substring(index);
dynamicLibraryPathsPresentInProject.Add(relativePath);
}
if (dynamicLibraryPathsPresentInProject.Count <= 0) return;
#if UNITY_2019_3_OR_NEWER
if (ShouldEmbedDynamicLibraries(buildPath))
{
foreach (var dynamicLibraryPath in dynamicLibraryPathsPresentInProject)
{
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
}
private static void LocalizeUserTrackingDescriptionIfNeeded(string localizedUserTrackingDescription, string localeCode, string buildPath, PBXProject project, string targetGuid)
{
// Use the legacy resources directory name if the build is being appended (the "Resources" directory already exists if it is an incremental build).
var resourcesDirectoryName = Directory.Exists(Path.Combine(buildPath, LegacyResourcesDirectoryName)) ? LegacyResourcesDirectoryName : AppLovinMaxResourcesDirectoryName;
var resourcesDirectoryPath = Path.Combine(buildPath, resourcesDirectoryName);
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;
}
// 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(resourcesDirectoryName, localeSpecificDirectoryName);
var guid = project.AddFolderReference(localeSpecificDirectoryRelativePath, localeSpecificDirectoryRelativePath);
project.AddFileToBuild(targetGuid, guid);
}
private static bool ShouldRemoveLocalization(string localizedUserTrackingDescription)
{
if (string.IsNullOrEmpty(localizedUserTrackingDescription)) return true;
var settings = AppLovinSettings.Instance;
var internalSettings = AppLovinInternalSettings.Instance;
return (!internalSettings.ConsentFlowEnabled || !internalSettings.UserTrackingUsageLocalizationEnabled)
&& (!settings.ConsentFlowEnabled || !settings.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, PBXSourceTree.Source);
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");
}
// Enable Swift modules
project.AddBuildProperty(unityFrameworkTargetGuid, "CLANG_ENABLE_MODULES", "YES");
project.AddBuildProperty(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();
}
}
[PostProcessBuildAttribute(int.MaxValue)]
public static void MaxPostProcessPlist(BuildTarget buildTarget, string path)
{
var plistPath = Path.Combine(path, "Info.plist");
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
SetSdkKeyIfNeeded(plist);
SetAttributionReportEndpointIfNeeded(plist);
EnableVerboseLoggingIfNeeded(plist);
AddGoogleApplicationIdIfNeeded(plist);
AddSdkSettingsIfNeeded(plist, path);
EnableTermsFlowIfNeeded(plist);
AddSkAdNetworksInfoIfNeeded(plist);
plist.WriteToFile(plistPath);
}
private static void SetSdkKeyIfNeeded(PlistDocument plist)
{
var sdkKey = AppLovinSettings.Instance.SdkKey;
if (string.IsNullOrEmpty(sdkKey)) return;
const string AppLovinVerboseLoggingOnKey = "AppLovinSdkKey";
plist.root.SetString(AppLovinVerboseLoggingOnKey, sdkKey);
}
private static void SetAttributionReportEndpointIfNeeded(PlistDocument plist)
{
if (AppLovinSettings.Instance.SetAttributionReportEndpoint)
{
plist.root.SetString("NSAdvertisingAttributionReportEndpoint", AppLovinAdvertisingAttributionEndpoint);
}
else
{
PlistElement attributionReportEndPoint;
plist.root.values.TryGetValue("NSAdvertisingAttributionReportEndpoint", out attributionReportEndPoint);
// Check if we had previously set the attribution endpoint and un-set it.
if (attributionReportEndPoint != null && AppLovinAdvertisingAttributionEndpoint.Equals(attributionReportEndPoint.AsString()))
{
plist.root.values.Remove("NSAdvertisingAttributionReportEndpoint");
}
}
}
private static void EnableVerboseLoggingIfNeeded(PlistDocument plist)
{
if (!EditorPrefs.HasKey(MaxSdkLogger.KeyVerboseLoggingEnabled)) return;
var enabled = EditorPrefs.GetBool(MaxSdkLogger.KeyVerboseLoggingEnabled);
const string AppLovinVerboseLoggingOnKey = "AppLovinVerboseLoggingOn";
if (enabled)
{
plist.root.SetBoolean(AppLovinVerboseLoggingOnKey, enabled);
}
else
{
plist.root.values.Remove(AppLovinVerboseLoggingOnKey);
}
}
private static void AddGoogleApplicationIdIfNeeded(PlistDocument plist)
{
if (!AppLovinIntegrationManager.IsAdapterInstalled("Google") && !AppLovinIntegrationManager.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-"))
{
Debug.LogError("[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 (!AppLovinIntegrationManager.IsAdapterInstalled("Yandex")) return;
if (MaxSdkUtils.CompareVersions(PlayerSettings.iOS.targetOSVersionString, "12.0") == MaxSdkUtils.VersionComparisonResult.Lesser)
{
Debug.LogWarning("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 AddSdkSettingsIfNeeded(PlistDocument infoPlist, string buildPath)
{
// Right now internal settings is only needed for Consent Flow. Remove this setting once we add more settings.
if (!AppLovinInternalSettings.Instance.ConsentFlowEnabled) return;
var sdkSettingsPlistPath = Path.Combine(buildPath, AppLovinSettingsPlistFileName);
var sdkSettingsPlist = new PlistDocument();
if (File.Exists(sdkSettingsPlistPath))
{
sdkSettingsPlist.ReadFromFile(sdkSettingsPlistPath);
}
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, PBXSourceTree.Source);
project.AddFileToBuild(unityMainTargetGuid, guid);
project.WriteToFile(projectPath);
}
private static void EnableConsentFlowIfNeeded(PlistDocument applovinSettingsPlist, PlistDocument infoPlist)
{
var consentFlowEnabled = AppLovinInternalSettings.Instance.ConsentFlowEnabled;
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 debugUserGeography = AppLovinInternalSettings.Instance.DebugUserGeography;
if (debugUserGeography == MaxSdkBase.ConsentFlowUserGeography.Gdpr)
{
consentFlowInfoRoot.SetString(KeyConsentFlowDebugUserGeography, "gdpr");
}
infoPlist.root.SetString("NSUserTrackingUsageDescription", userTrackingUsageDescription);
}
private static void EnableTermsFlowIfNeeded(PlistDocument plist)
{
// Check if terms flow is enabled. No need to update info.plist if consent flow is disabled.
var consentFlowEnabled = AppLovinSettings.Instance.ConsentFlowEnabled;
if (!consentFlowEnabled) return;
// Check if terms flow is enabled for this format.
var consentFlowPlatform = AppLovinSettings.Instance.ConsentFlowPlatform;
if (consentFlowPlatform != Platform.All && consentFlowPlatform != Platform.iOS) return;
var userTrackingUsageDescription = AppLovinSettings.Instance.UserTrackingUsageDescriptionEn;
var privacyPolicyUrl = AppLovinSettings.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 = plist.root.CreateDict("AppLovinConsentFlowInfo");
consentFlowInfoRoot.SetBoolean("AppLovinConsentFlowEnabled", consentFlowEnabled);
consentFlowInfoRoot.SetString("AppLovinConsentFlowPrivacyPolicy", privacyPolicyUrl);
var termsOfServiceUrl = AppLovinSettings.Instance.ConsentFlowTermsOfServiceUrl;
if (!string.IsNullOrEmpty(termsOfServiceUrl))
{
consentFlowInfoRoot.SetString("AppLovinConsentFlowTermsOfService", termsOfServiceUrl);
}
plist.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()
{
var uriBuilder = new UriBuilder("https://unity.applovin.com/max/1.0/skadnetwork_ids");
// Get the list of installed ad networks to be passed up
var maxMediationDirectory = PluginMediationDirectory;
if (Directory.Exists(maxMediationDirectory))
{
var mediationNetworkDirectories = Directory.GetDirectories(maxMediationDirectory);
var installedNetworks = mediationNetworkDirectories.Select(Path.GetFileName).ToList();
if (AppLovinSettings.Instance.AddApsSkAdNetworkIds)
{
installedNetworks.Add("AmazonPublisherServices");
}
var adNetworks = string.Join(",", installedNetworks.ToArray());
if (!string.IsNullOrEmpty(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();
}
}
}
#if UNITY_2019_3_OR_NEWER
/// <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;
}
#endif
}
}
#endif