using System; using System.IO; using System.Xml; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Text.RegularExpressions; using UnityEngine; using UnityEditor; using UnityEditor.Callbacks; #if UNITY_IOS using UnityEditor.iOS.Xcode; #endif public class AdjustEditor : AssetPostprocessor { private static bool isPostProcessingEnabled = true; [MenuItem("Assets/Adjust/Check Post Processing Permission")] public static void CheckPostProcessingPermission() { EditorUtility.DisplayDialog("Adjust SDK", "The post processing for Adjust SDK is " + (isPostProcessingEnabled ? "enabled." : "disabled."), "OK"); } [MenuItem("Assets/Adjust/Change Post Processing Permission")] public static void ChangePostProcessingPermission() { isPostProcessingEnabled = !isPostProcessingEnabled; EditorUtility.DisplayDialog("Adjust SDK", "The post processing for Adjust SDK is now " + (isPostProcessingEnabled ? "enabled." : "disabled."), "OK"); } [MenuItem("Assets/Adjust/Export Unity Package")] static void ExportAdjustUnityPackage() { string exportedFileName = "Adjust.unitypackage"; string assetsPath = "Assets/Adjust"; List assetsToExport = new List(); // Adjust Assets. assetsToExport.Add(assetsPath + "/3rd Party/SimpleJSON.cs"); assetsToExport.Add(assetsPath + "/Android/adjust-android.jar"); assetsToExport.Add(assetsPath + "/Android/AdjustAndroid.cs"); assetsToExport.Add(assetsPath + "/Android/AdjustAndroidManifest.xml"); assetsToExport.Add(assetsPath + "/Editor/AdjustEditor.cs"); assetsToExport.Add(assetsPath + "/ExampleGUI/ExampleGUI.cs"); assetsToExport.Add(assetsPath + "/ExampleGUI/ExampleGUI.prefab"); assetsToExport.Add(assetsPath + "/ExampleGUI/ExampleGUI.unity"); assetsToExport.Add(assetsPath + "/iOS/ADJAttribution.h"); assetsToExport.Add(assetsPath + "/iOS/ADJConfig.h"); assetsToExport.Add(assetsPath + "/iOS/ADJEvent.h"); assetsToExport.Add(assetsPath + "/iOS/ADJEventFailure.h"); assetsToExport.Add(assetsPath + "/iOS/ADJEventSuccess.h"); assetsToExport.Add(assetsPath + "/iOS/ADJLogger.h"); assetsToExport.Add(assetsPath + "/iOS/ADJSessionFailure.h"); assetsToExport.Add(assetsPath + "/iOS/ADJSessionSuccess.h"); assetsToExport.Add(assetsPath + "/iOS/ADJSubscription.h"); assetsToExport.Add(assetsPath + "/iOS/Adjust.h"); assetsToExport.Add(assetsPath + "/iOS/AdjustiOS.cs"); assetsToExport.Add(assetsPath + "/iOS/AdjustSdk.a"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnity.h"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnity.mm"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnityDelegate.h"); assetsToExport.Add(assetsPath + "/iOS/AdjustUnityDelegate.mm"); assetsToExport.Add(assetsPath + "/Prefab/Adjust.prefab"); assetsToExport.Add(assetsPath + "/Unity/Adjust.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustAppStoreSubscription.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustAttribution.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustConfig.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEnvironment.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEvent.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEventFailure.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustEventSuccess.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustLogLevel.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustPlayStoreSubscription.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustSessionFailure.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustSessionSuccess.cs"); assetsToExport.Add(assetsPath + "/Unity/AdjustUtils.cs"); assetsToExport.Add(assetsPath + "/Windows/AdjustWindows.cs"); assetsToExport.Add(assetsPath + "/Windows/WindowsPcl.dll"); assetsToExport.Add(assetsPath + "/Windows/WindowsUap.dll"); assetsToExport.Add(assetsPath + "/Windows/Stubs/Win10Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/Stubs/Win81Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/Stubs/WinWsInterface.dll"); assetsToExport.Add(assetsPath + "/Windows/W81/AdjustWP81.dll"); assetsToExport.Add(assetsPath + "/Windows/W81/Win81Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/WS/AdjustWS.dll"); assetsToExport.Add(assetsPath + "/Windows/WS/WinWsInterface.dll"); assetsToExport.Add(assetsPath + "/Windows/WU10/AdjustUAP10.dll"); assetsToExport.Add(assetsPath + "/Windows/WU10/Win10Interface.dll"); assetsToExport.Add(assetsPath + "/Windows/Newtonsoft.Json.dll"); AssetDatabase.ExportPackage( assetsToExport.ToArray(), exportedFileName, ExportPackageOptions.IncludeDependencies | ExportPackageOptions.Interactive); } [PostProcessBuild] public static void OnPostprocessBuild(BuildTarget target, string projectPath) { // Check what is user setting about allowing Adjust SDK to perform post build tasks. // If user disabled it, oh well, we won't do a thing. if (!isPostProcessingEnabled) { UnityEngine.Debug.Log("[Adjust]: You have forbidden the Adjust SDK to perform post processing tasks."); UnityEngine.Debug.Log("[Adjust]: Skipping post processing tasks."); return; } RunPostBuildScript(target:target, preBuild:false, projectPath:projectPath); } private static void RunPostBuildScript(BuildTarget target, bool preBuild, string projectPath = "") { if (target == BuildTarget.Android) { UnityEngine.Debug.Log("[Adjust]: Starting to perform post build tasks for Android platform."); RunPostProcessTasksAndroid(); } else if (target == BuildTarget.iOS) { #if UNITY_IOS UnityEngine.Debug.Log("[Adjust]: Starting to perform post build tasks for iOS platform."); string xcodeProjectPath = projectPath + "/Unity-iPhone.xcodeproj/project.pbxproj"; PBXProject xcodeProject = new PBXProject(); xcodeProject.ReadFromFile(xcodeProjectPath); // The Adjust SDK needs two frameworks to be added to the project: // - AdSupport.framework // - iAd.framework #if UNITY_2019_3_OR_NEWER string xcodeTarget = xcodeProject.GetUnityFrameworkTargetGuid(); #else string xcodeTarget = xcodeProject.TargetGuidByName("Unity-iPhone"); #endif UnityEngine.Debug.Log("[Adjust]: Adding AdSupport.framework to Xcode project."); xcodeProject.AddFrameworkToProject(xcodeTarget, "AdSupport.framework", true); UnityEngine.Debug.Log("[Adjust]: AdSupport.framework added successfully."); UnityEngine.Debug.Log("[Adjust]: Adding iAd.framework to Xcode project."); xcodeProject.AddFrameworkToProject(xcodeTarget, "iAd.framework", true); UnityEngine.Debug.Log("[Adjust]: iAd.framework added successfully."); UnityEngine.Debug.Log("[Adjust]: Adding CoreTelephony.framework to Xcode project."); xcodeProject.AddFrameworkToProject(xcodeTarget, "CoreTelephony.framework", true); UnityEngine.Debug.Log("[Adjust]: CoreTelephony.framework added successfully."); // The Adjust SDK needs to have Obj-C exceptions enabled. // GCC_ENABLE_OBJC_EXCEPTIONS=YES UnityEngine.Debug.Log("[Adjust]: Enabling Obj-C exceptions by setting GCC_ENABLE_OBJC_EXCEPTIONS value to YES."); xcodeProject.AddBuildProperty(xcodeTarget, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES"); UnityEngine.Debug.Log("[Adjust]: Obj-C exceptions enabled successfully."); // The Adjust SDK needs to have -ObjC flag set in other linker flags section because of it's categories. // OTHER_LDFLAGS -ObjC UnityEngine.Debug.Log("[Adjust]: Adding -ObjC flag to other linker flags (OTHER_LDFLAGS)."); xcodeProject.AddBuildProperty(xcodeTarget, "OTHER_LDFLAGS", "-ObjC"); UnityEngine.Debug.Log("[Adjust]: -ObjC successfully added to other linker flags."); // Save the changes to Xcode project file. xcodeProject.WriteToFile(xcodeProjectPath); #endif } } private static void RunPostProcessTasksiOS(string projectPath) {} private static void RunPostProcessTasksAndroid() { bool isAdjustManifestUsed = false; string androidPluginsPath = Path.Combine(Application.dataPath, "Plugins/Android"); string adjustManifestPath = Path.Combine(Application.dataPath, "Adjust/Android/AdjustAndroidManifest.xml"); string appManifestPath = Path.Combine(Application.dataPath, "Plugins/Android/AndroidManifest.xml"); // Check if user has already created AndroidManifest.xml file in its location. // If not, use already predefined AdjustAndroidManifest.xml as default one. if (!File.Exists(appManifestPath)) { if (!Directory.Exists(androidPluginsPath)) { Directory.CreateDirectory(androidPluginsPath); } isAdjustManifestUsed = true; File.Copy(adjustManifestPath, appManifestPath); UnityEngine.Debug.Log("[Adjust]: User defined AndroidManifest.xml file not found in Plugins/Android folder."); UnityEngine.Debug.Log("[Adjust]: Creating default app's AndroidManifest.xml from AdjustAndroidManifest.xml file."); } else { UnityEngine.Debug.Log("[Adjust]: User defined AndroidManifest.xml file located in Plugins/Android folder."); } // If Adjust manifest is used, we have already set up everything in it so that // our native Android SDK can be used properly. if (!isAdjustManifestUsed) { // However, if you already had your own AndroidManifest.xml, we'll now run // some checks on it and tweak it a bit if needed to add some stuff which // our native Android SDK needs so that it can run properly. // Let's open the app's AndroidManifest.xml file. XmlDocument manifestFile = new XmlDocument(); manifestFile.Load(appManifestPath); bool manifestHasChanged = false; // Add needed permissions if they are missing. manifestHasChanged |= AddPermissions(manifestFile); // Add intent filter to main activity if it is missing. manifestHasChanged |= AddBroadcastReceiver(manifestFile); if (manifestHasChanged) { // Save the changes. manifestFile.Save(appManifestPath); // Clean the manifest file. CleanManifestFile(appManifestPath); UnityEngine.Debug.Log("[Adjust]: App's AndroidManifest.xml file check and potential modification completed."); UnityEngine.Debug.Log("[Adjust]: Please check if any error message was displayed during this process " + "and make sure to fix all issues in order to properly use the Adjust SDK in your app."); } else { UnityEngine.Debug.Log("[Adjust]: App's AndroidManifest.xml file check completed."); UnityEngine.Debug.Log("[Adjust]: No modifications performed due to app's AndroidManifest.xml file compatibility."); } } } private static bool AddPermissions(XmlDocument manifest) { // The Adjust SDK needs two permissions to be added to you app's manifest file: // // // // UnityEngine.Debug.Log("[Adjust]: Checking if all permissions needed for the Adjust SDK are present in the app's AndroidManifest.xml file."); bool hasInternetPermission = false; bool hasAccessWifiStatePermission = false; bool hasAccessNetworkStatePermission = false; bool hasInstallReferrerServicePermission = false; XmlElement manifestRoot = manifest.DocumentElement; // Check if permissions are already there. foreach (XmlNode node in manifestRoot.ChildNodes) { if (node.Name == "uses-permission") { foreach (XmlAttribute attribute in node.Attributes) { if (attribute.Value.Contains("android.permission.INTERNET")) { hasInternetPermission = true; } else if (attribute.Value.Contains("android.permission.ACCESS_WIFI_STATE")) { hasAccessWifiStatePermission = true; } else if (attribute.Value.Contains("android.permission.ACCESS_NETWORK_STATE")) { hasAccessNetworkStatePermission = true; } else if (attribute.Value.Contains("com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE")) { hasInstallReferrerServicePermission = true; } } } } bool manifestHasChanged = false; // If android.permission.INTERNET permission is missing, add it. if (!hasInternetPermission) { XmlElement element = manifest.CreateElement("uses-permission"); element.SetAttribute("android__name", "android.permission.INTERNET"); manifestRoot.AppendChild(element); UnityEngine.Debug.Log("[Adjust]: android.permission.INTERNET permission successfully added to your app's AndroidManifest.xml file."); manifestHasChanged = true; } else { UnityEngine.Debug.Log("[Adjust]: Your app's AndroidManifest.xml file already contains android.permission.INTERNET permission."); } // If android.permission.ACCESS_WIFI_STATE permission is missing, add it. if (!hasAccessWifiStatePermission) { XmlElement element = manifest.CreateElement("uses-permission"); element.SetAttribute("android__name", "android.permission.ACCESS_WIFI_STATE"); manifestRoot.AppendChild(element); UnityEngine.Debug.Log("[Adjust]: android.permission.ACCESS_WIFI_STATE permission successfully added to your app's AndroidManifest.xml file."); manifestHasChanged = true; } else { UnityEngine.Debug.Log("[Adjust]: Your app's AndroidManifest.xml file already contains android.permission.ACCESS_WIFI_STATE permission."); } // If android.permission.ACCESS_NETWORK_STATE permission is missing, add it. if (!hasAccessNetworkStatePermission) { XmlElement element = manifest.CreateElement("uses-permission"); element.SetAttribute("android__name", "android.permission.ACCESS_NETWORK_STATE"); manifestRoot.AppendChild(element); UnityEngine.Debug.Log("[Adjust]: android.permission.ACCESS_NETWORK_STATE permission successfully added to your app's AndroidManifest.xml file."); manifestHasChanged = true; } else { UnityEngine.Debug.Log("[Adjust]: Your app's AndroidManifest.xml file already contains android.permission.ACCESS_NETWORK_STATE permission."); } // If com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE permission is missing, add it. if (!hasInstallReferrerServicePermission) { XmlElement element = manifest.CreateElement("uses-permission"); element.SetAttribute("android__name", "com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"); manifestRoot.AppendChild(element); UnityEngine.Debug.Log("[Adjust]: com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE permission successfully added to your app's AndroidManifest.xml file."); manifestHasChanged = true; } else { UnityEngine.Debug.Log("[Adjust]: Your app's AndroidManifest.xml file already contains com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE permission."); } return manifestHasChanged; } private static bool AddBroadcastReceiver(XmlDocument manifest) { // We're looking for existance of broadcast receiver in the AndroidManifest.xml // Check out the example below how that usually looks like: // > // // /> // // > // // // // // // // // // // // // > // // UnityEngine.Debug.Log("[Adjust]: Checking if app's AndroidManifest.xml file contains receiver for INSTALL_REFERRER intent."); XmlElement manifestRoot = manifest.DocumentElement; XmlNode applicationNode = null; // Let's find the application node. foreach(XmlNode node in manifestRoot.ChildNodes) { if (node.Name == "application") { applicationNode = node; break; } } // If there's no applicatio node, something is really wrong with your AndroidManifest.xml. if (applicationNode == null) { UnityEngine.Debug.LogError("[Adjust]: Your app's AndroidManifest.xml file does not contain \"\" node."); UnityEngine.Debug.LogError("[Adjust]: Unable to add the Adjust broadcast receiver to AndroidManifest.xml."); return false; } // Okay, there's an application node in the AndroidManifest.xml file. // Let's now check if user has already defined a receiver which is listening to INSTALL_REFERRER intent. // If that is already defined, don't force the Adjust broadcast receiver to the manifest file. // If not, add the Adjust broadcast receiver to the manifest file. List customBroadcastReceiversNodes = getCustomRecieverNodes(applicationNode); if (customBroadcastReceiversNodes.Count > 0) { bool foundAdjustBroadcastReceiver = false; for (int i = 0; i < customBroadcastReceiversNodes.Count; i += 1) { foreach (XmlAttribute attribute in customBroadcastReceiversNodes[i].Attributes) { if (attribute.Value.Contains("com.adjust.sdk.AdjustReferrerReceiver")) { foundAdjustBroadcastReceiver = true; } } } if (!foundAdjustBroadcastReceiver) { UnityEngine.Debug.Log("[Adjust]: It seems like you are using your own broadcast receiver."); UnityEngine.Debug.Log("[Adjust]: Please, add the calls to the Adjust broadcast receiver like described in here: https://github.com/adjust/android_sdk/blob/master/doc/english/referrer.md"); } else { UnityEngine.Debug.Log("[Adjust]: It seems like you are already using Adjust broadcast receiver. Yay."); } return false; } else { // Generate Adjust broadcast receiver entry and add it to the application node. XmlElement receiverElement = manifest.CreateElement("receiver"); receiverElement.SetAttribute("android__name", "com.adjust.sdk.AdjustReferrerReceiver"); receiverElement.SetAttribute("android__permission", "android.permission.INSTALL_PACKAGES"); receiverElement.SetAttribute("android__exported", "true"); XmlElement intentFilterElement = manifest.CreateElement("intent-filter"); XmlElement actionElement = manifest.CreateElement("action"); actionElement.SetAttribute("android__name", "com.android.vending.INSTALL_REFERRER"); intentFilterElement.AppendChild(actionElement); receiverElement.AppendChild(intentFilterElement); applicationNode.AppendChild(receiverElement); UnityEngine.Debug.Log("[Adjust]: Adjust broadcast receiver successfully added to your app's AndroidManifest.xml file."); return true; } } private static void CleanManifestFile(String manifestPath) { // Due to XML writing issue with XmlElement methods which are unable // to write "android:[param]" string, we have wrote "android__[param]" string instead. // Now make the replacement: "android:[param]" -> "android__[param]" TextReader manifestReader = new StreamReader(manifestPath); string manifestContent = manifestReader.ReadToEnd(); manifestReader.Close(); Regex regex = new Regex("android__"); manifestContent = regex.Replace(manifestContent, "android:"); TextWriter manifestWriter = new StreamWriter(manifestPath); manifestWriter.Write(manifestContent); manifestWriter.Close(); } private static List getCustomRecieverNodes(XmlNode applicationNode) { List nodes = new List(); foreach (XmlNode node in applicationNode.ChildNodes) { if (node.Name == "receiver") { foreach (XmlNode subnode in node.ChildNodes) { if (subnode.Name == "intent-filter") { foreach (XmlNode subsubnode in subnode.ChildNodes) { if (subsubnode.Name == "action") { foreach (XmlAttribute attribute in subsubnode.Attributes) { if (attribute.Value.Contains("com.android.vending.INSTALL_REFERRER")) { nodes.Add(node); } } } } } } } } return nodes; } }