366 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
		
		
			
		
	
	
			366 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
|  | using System.IO; | |||
|  | using System.Collections; | |||
|  | using System.Collections.Generic; | |||
|  | using UnityEngine; | |||
|  | using UnityEditor.Build; | |||
|  | using UnityEditor; | |||
|  | using System.Xml; | |||
|  | using System; | |||
|  | using System.Text.RegularExpressions; | |||
|  | using System.Linq; | |||
|  | 
 | |||
|  | #if UNITY_2018_1_OR_NEWER | |||
|  | public class AdjustEditorPreprocessor : IPreprocessBuildWithReport | |||
|  | #else | |||
|  | public class AdjustEditorPreprocessor : IPreprocessBuild | |||
|  | #endif | |||
|  | { | |||
|  |     public int callbackOrder | |||
|  |     { | |||
|  |         get | |||
|  |         { | |||
|  |             return 0; | |||
|  |         } | |||
|  |     } | |||
|  | #if UNITY_2018_1_OR_NEWER | |||
|  |     public void OnPreprocessBuild(UnityEditor.Build.Reporting.BuildReport report) | |||
|  |     { | |||
|  |         OnPreprocessBuild(report.summary.platform, string.Empty); | |||
|  |     } | |||
|  | #endif | |||
|  | 
 | |||
|  |     public void OnPreprocessBuild(BuildTarget target, string path) | |||
|  |     { | |||
|  |         if (target == BuildTarget.Android) | |||
|  |         { | |||
|  | #if UNITY_ANDROID | |||
|  |             RunPostProcessTasksAndroid(); | |||
|  | #endif | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  | #if UNITY_ANDROID | |||
|  |     private static void RunPostProcessTasksAndroid() | |||
|  |     { | |||
|  |         var isAdjustManifestUsed = false; | |||
|  |         var androidPluginsPath = Path.Combine(Application.dataPath, "Plugins/Android"); | |||
|  |         var adjustManifestPath = Path.Combine(Application.dataPath, "Adjust/Android/AdjustAndroidManifest.xml"); | |||
|  |         var 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); | |||
|  | 
 | |||
|  |             Debug.Log("[Adjust]: User defined AndroidManifest.xml file not found in Plugins/Android folder."); | |||
|  |             Debug.Log("[Adjust]: Creating default app's AndroidManifest.xml from AdjustAndroidManifest.xml file."); | |||
|  |         } | |||
|  |         else | |||
|  |         { | |||
|  |             Debug.Log("[Adjust]: User defined AndroidManifest.xml file located in Plugins/Android folder."); | |||
|  |         } | |||
|  | 
 | |||
|  |         // Let's open the app's AndroidManifest.xml file. | |||
|  |         var manifestFile = new XmlDocument(); | |||
|  |         manifestFile.Load(appManifestPath); | |||
|  | 
 | |||
|  |         var manifestHasChanged = false; | |||
|  | 
 | |||
|  |         // 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. | |||
|  | 
 | |||
|  |             // Add needed permissions if they are missing. | |||
|  |             manifestHasChanged |= AddPermissions(manifestFile); | |||
|  | 
 | |||
|  |             // Add intent filter to main activity if it is missing. | |||
|  |             manifestHasChanged |= AddBroadcastReceiver(manifestFile); | |||
|  |         } | |||
|  | 
 | |||
|  |         // Add intent filter to URL schemes for deeplinking | |||
|  |         manifestHasChanged |= AddURISchemes(manifestFile); | |||
|  | 
 | |||
|  |         if (manifestHasChanged) | |||
|  |         { | |||
|  |             // Save the changes. | |||
|  |             manifestFile.Save(appManifestPath); | |||
|  | 
 | |||
|  |             Debug.Log("[Adjust]: App's AndroidManifest.xml file check and potential modification completed."); | |||
|  |             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 | |||
|  |         { | |||
|  |             Debug.Log("[Adjust]: App's AndroidManifest.xml file check completed."); | |||
|  |             Debug.Log("[Adjust]: No modifications performed due to app's AndroidManifest.xml file compatibility."); | |||
|  |         } | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool AddURISchemes(XmlDocument manifest) | |||
|  |     { | |||
|  |         if (AdjustSettings.AndroidUriSchemes.Length == 0) | |||
|  |         { | |||
|  |             return false; | |||
|  |         } | |||
|  |         Debug.Log("[Adjust]: Start addition of URI schemes"); | |||
|  | 
 | |||
|  |         var intentRoot = manifest.DocumentElement.SelectSingleNode("/manifest/application/activity[@android:name='com.unity3d.player.UnityPlayerActivity']", GetNamespaceManager(manifest)); | |||
|  |         var usedIntentFiltersChanged = false; | |||
|  |         var usedIntentFilters = GetIntentFilter(manifest); | |||
|  |         foreach (var uriScheme in AdjustSettings.AndroidUriSchemes) | |||
|  |         { | |||
|  |             Uri uri; | |||
|  |             try | |||
|  |             { | |||
|  |                 // The first element is android:scheme and the second one is android:host. | |||
|  |                 uri = new Uri(uriScheme); | |||
|  | 
 | |||
|  |                 // Uri class converts implicit file paths to explicit file paths with the file:// scheme. | |||
|  |                 if (!uriScheme.StartsWith(uri.Scheme)) | |||
|  |                 { | |||
|  |                     throw new UriFormatException(); | |||
|  |                 } | |||
|  |             } | |||
|  |             catch (UriFormatException) | |||
|  |             { | |||
|  |                 Debug.LogError(string.Format("[Adjust]: Android deeplink URI scheme \"{0}\" is invalid and will be ignored.", uriScheme)); | |||
|  |                 Debug.LogWarning(string.Format("[Adjust]: Make sure that your URI scheme entry ends with ://")); | |||
|  |                 continue; | |||
|  |             } | |||
|  | 
 | |||
|  |             if (!IsIntentFilterAlreadyExist(manifest, uri)) | |||
|  |             { | |||
|  |                 Debug.Log("[Adjust]: Adding new URI with scheme: " + uri.Scheme + ", and host: " + uri.Host); | |||
|  |                 var androidSchemeNode = manifest.CreateElement("data"); | |||
|  |                 AddAndroidNamespaceAttribute(manifest, "scheme", uri.Scheme, androidSchemeNode); | |||
|  |                 AddAndroidNamespaceAttribute(manifest, "host", uri.Host, androidSchemeNode); | |||
|  |                 usedIntentFilters.AppendChild(androidSchemeNode); | |||
|  |                 usedIntentFiltersChanged = true; | |||
|  | 
 | |||
|  |                 Debug.Log(string.Format("[Adjust]: Android deeplink URI scheme \"{0}\" successfully added to your app's AndroidManifest.xml file.", uriScheme)); | |||
|  |             } | |||
|  |         } | |||
|  | 
 | |||
|  |         if (usedIntentFiltersChanged && usedIntentFilters.ParentNode == null) | |||
|  |         { | |||
|  |             intentRoot.AppendChild(usedIntentFilters); | |||
|  |         } | |||
|  | 
 | |||
|  |         return usedIntentFiltersChanged; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static XmlElement GetIntentFilter(XmlDocument manifest) | |||
|  |     { | |||
|  |         var xpath = "/manifest/application/activity/intent-filter[data/@android:scheme and data/@android:host]"; | |||
|  |         var intentFilter = manifest.DocumentElement.SelectSingleNode(xpath, GetNamespaceManager(manifest)) as XmlElement; | |||
|  |         if (intentFilter == null) | |||
|  |         { | |||
|  |             const string androidName = "name"; | |||
|  |             const string category = "category"; | |||
|  | 
 | |||
|  |             intentFilter = manifest.CreateElement("intent-filter"); | |||
|  | 
 | |||
|  |             var actionElement = manifest.CreateElement("action"); | |||
|  |             AddAndroidNamespaceAttribute(manifest, androidName, "android.intent.action.VIEW", actionElement); | |||
|  |             intentFilter.AppendChild(actionElement); | |||
|  | 
 | |||
|  |             var defaultCategory = manifest.CreateElement(category); | |||
|  |             AddAndroidNamespaceAttribute(manifest, androidName, "android.intent.category.DEFAULT", defaultCategory); | |||
|  |             intentFilter.AppendChild(defaultCategory); | |||
|  | 
 | |||
|  |             var browsableCategory = manifest.CreateElement(category); | |||
|  |             AddAndroidNamespaceAttribute(manifest, androidName, "android.intent.category.BROWSABLE", browsableCategory); | |||
|  |             intentFilter.AppendChild(browsableCategory); | |||
|  |         } | |||
|  |         return intentFilter; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool IsIntentFilterAlreadyExist(XmlDocument manifest, Uri link) | |||
|  |     { | |||
|  |         var xpath = string.Format("/manifest/application/activity/intent-filter/data[@android:scheme='{0}' and @android:host='{1}']", link.Scheme, link.Host); | |||
|  |         return manifest.DocumentElement.SelectSingleNode(xpath, GetNamespaceManager(manifest)) != null; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool AddPermissions(XmlDocument manifest) | |||
|  |     { | |||
|  |         // The Adjust SDK needs two permissions to be added to you app's manifest file: | |||
|  |         // <uses-permission android:name="android.permission.INTERNET" /> | |||
|  |         // <uses-permission android:name="com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE" /> | |||
|  |         // <uses-permission android:name="com.google.android.gms.permission.AD_ID"/> | |||
|  |         // <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | |||
|  | 
 | |||
|  |         Debug.Log("[Adjust]: Checking if all permissions needed for the Adjust SDK are present in the app's AndroidManifest.xml file."); | |||
|  | 
 | |||
|  |         var manifestHasChanged = false; | |||
|  | 
 | |||
|  |         // If enabled by the user && android.permission.INTERNET permission is missing, add it. | |||
|  |         if (AdjustSettings.androidPermissionInternet == true) | |||
|  |         { | |||
|  |             manifestHasChanged |= AddPermission(manifest, "android.permission.INTERNET"); | |||
|  |         } | |||
|  |         // If enabled by the user && com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE permission is missing, add it. | |||
|  |         if (AdjustSettings.androidPermissionInstallReferrerService == true) | |||
|  |         { | |||
|  |             manifestHasChanged |= AddPermission(manifest, "com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE"); | |||
|  |         } | |||
|  |         // If enabled by the user && com.google.android.gms.permission.AD_ID permission is missing, add it. | |||
|  |         if (AdjustSettings.androidPermissionAdId == true) | |||
|  |         { | |||
|  |             manifestHasChanged |= AddPermission(manifest, "com.google.android.gms.permission.AD_ID"); | |||
|  |         } | |||
|  |         // If enabled by the user && android.permission.ACCESS_NETWORK_STATE permission is missing, add it. | |||
|  |         if (AdjustSettings.androidPermissionAccessNetworkState == true) | |||
|  |         { | |||
|  |             manifestHasChanged |= AddPermission(manifest, "android.permission.ACCESS_NETWORK_STATE"); | |||
|  |         } | |||
|  | 
 | |||
|  |         return manifestHasChanged; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool AddPermission(XmlDocument manifest, string permissionValue) | |||
|  |     { | |||
|  |         if (DoesPermissionExist(manifest, permissionValue)) | |||
|  |         { | |||
|  |             Debug.Log(string.Format("[Adjust]: Your app's AndroidManifest.xml file already contains {0} permission.", permissionValue)); | |||
|  |             return false; | |||
|  |         } | |||
|  | 
 | |||
|  |         var element = manifest.CreateElement("uses-permission"); | |||
|  |         AddAndroidNamespaceAttribute(manifest, "name", permissionValue, element); | |||
|  |         manifest.DocumentElement.AppendChild(element); | |||
|  |         Debug.Log(string.Format("[Adjust]: {0} permission successfully added to your app's AndroidManifest.xml file.", permissionValue)); | |||
|  | 
 | |||
|  |         return true; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool DoesPermissionExist(XmlDocument manifest, string permissionValue) | |||
|  |     { | |||
|  |         var xpath = string.Format("/manifest/uses-permission[@android:name='{0}']", permissionValue); | |||
|  |         return manifest.DocumentElement.SelectSingleNode(xpath, GetNamespaceManager(manifest)) != null; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool AddBroadcastReceiver(XmlDocument manifest) | |||
|  |     { | |||
|  |         // We're looking for existence of broadcast receiver in the AndroidManifest.xml | |||
|  |         // Check out the example below how that usually looks like: | |||
|  | 
 | |||
|  |         // <manifest | |||
|  |         //     <!-- ... -->> | |||
|  |         //  | |||
|  |         //     <supports-screens | |||
|  |         //         <!-- ... -->/> | |||
|  |         //  | |||
|  |         //     <application | |||
|  |         //         <!-- ... -->> | |||
|  |         //         <receiver | |||
|  |         //             android:name="com.adjust.sdk.AdjustReferrerReceiver" | |||
|  |         //             android:permission="android.permission.INSTALL_PACKAGES" | |||
|  |         //             android:exported="true" > | |||
|  |         //              | |||
|  |         //             <intent-filter> | |||
|  |         //                 <action android:name="com.android.vending.INSTALL_REFERRER" /> | |||
|  |         //             </intent-filter> | |||
|  |         //         </receiver> | |||
|  |         //          | |||
|  |         //         <activity android:name="com.unity3d.player.UnityPlayerActivity" | |||
|  |         //             <!-- ... --> | |||
|  |         //         </activity> | |||
|  |         //     </application> | |||
|  |         //  | |||
|  |         //     <!-- ... -->> | |||
|  |         // | |||
|  |         // </manifest> | |||
|  | 
 | |||
|  |         Debug.Log("[Adjust]: Checking if app's AndroidManifest.xml file contains receiver for INSTALL_REFERRER intent."); | |||
|  | 
 | |||
|  |         // Find the application node | |||
|  |         var applicationNodeXpath = "/manifest/application"; | |||
|  |         var applicationNode = manifest.DocumentElement.SelectSingleNode(applicationNodeXpath); | |||
|  | 
 | |||
|  |         // If there's no application node, something is really wrong with your AndroidManifest.xml. | |||
|  |         if (applicationNode == null) | |||
|  |         { | |||
|  |             Debug.LogError("[Adjust]: Your app's AndroidManifest.xml file does not contain \"<application>\" node."); | |||
|  |             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. | |||
|  | 
 | |||
|  |         var customBroadcastReceiversNodes = GetCustomRecieverNodes(manifest); | |||
|  |         if (customBroadcastReceiversNodes.Count > 0) | |||
|  |         { | |||
|  |             if (DoesAdjustBroadcastReceiverExist(manifest)) | |||
|  |             { | |||
|  |                 Debug.Log("[Adjust]: It seems like you are already using Adjust broadcast receiver. Yay."); | |||
|  |             } | |||
|  |             else | |||
|  |             { | |||
|  |                 Debug.Log("[Adjust]: It seems like you are using your own broadcast receiver."); | |||
|  |                 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"); | |||
|  |             } | |||
|  | 
 | |||
|  |             return false; | |||
|  |         } | |||
|  | 
 | |||
|  |         // Generate Adjust broadcast receiver entry and add it to the application node. | |||
|  |         var receiverElement = manifest.CreateElement("receiver"); | |||
|  |         AddAndroidNamespaceAttribute(manifest, "name", "com.adjust.sdk.AdjustReferrerReceiver", receiverElement); | |||
|  |         AddAndroidNamespaceAttribute(manifest, "permission", "android.permission.INSTALL_PACKAGES", receiverElement); | |||
|  |         AddAndroidNamespaceAttribute(manifest, "exported", "true", receiverElement); | |||
|  | 
 | |||
|  |         var intentFilterElement = manifest.CreateElement("intent-filter"); | |||
|  |         var actionElement = manifest.CreateElement("action"); | |||
|  |         AddAndroidNamespaceAttribute(manifest, "name", "com.android.vending.INSTALL_REFERRER", actionElement); | |||
|  | 
 | |||
|  |         intentFilterElement.AppendChild(actionElement); | |||
|  |         receiverElement.AppendChild(intentFilterElement); | |||
|  |         applicationNode.AppendChild(receiverElement); | |||
|  | 
 | |||
|  |         Debug.Log("[Adjust]: Adjust broadcast receiver successfully added to your app's AndroidManifest.xml file."); | |||
|  | 
 | |||
|  |         return true; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static bool DoesAdjustBroadcastReceiverExist(XmlDocument manifest) | |||
|  |     { | |||
|  |         var xpath = "/manifest/application/receiver[@android:name='com.adjust.sdk.AdjustReferrerReceiver']"; | |||
|  |         return manifest.SelectSingleNode(xpath, GetNamespaceManager(manifest)) != null; | |||
|  |     } | |||
|  | 
 | |||
|  |     private static List<XmlNode> GetCustomRecieverNodes(XmlDocument manifest) | |||
|  |     { | |||
|  |         var xpath = "/manifest/application/receiver[intent-filter/action[@android:name='com.android.vending.INSTALL_REFERRER']]"; | |||
|  |         return new List<XmlNode>(manifest.DocumentElement.SelectNodes(xpath, GetNamespaceManager(manifest)).OfType<XmlNode>()); | |||
|  |     } | |||
|  | 
 | |||
|  |     private static void AddAndroidNamespaceAttribute(XmlDocument manifest, string key, string value, XmlElement node) | |||
|  |     { | |||
|  |         var androidSchemeAttribute = manifest.CreateAttribute("android", key, "http://schemas.android.com/apk/res/android"); | |||
|  |         androidSchemeAttribute.InnerText = value; | |||
|  |         node.SetAttributeNode(androidSchemeAttribute); | |||
|  |     } | |||
|  | 
 | |||
|  |     private static XmlNamespaceManager GetNamespaceManager(XmlDocument manifest) | |||
|  |     { | |||
|  |         var namespaceManager = new XmlNamespaceManager(manifest.NameTable); | |||
|  |         namespaceManager.AddNamespace("android", "http://schemas.android.com/apk/res/android"); | |||
|  |         return namespaceManager; | |||
|  |     } | |||
|  | #endif | |||
|  | } |