#if UNITY_ANDROID using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using UnityEditor; using UnityEditor.Android; using UnityEngine; namespace Tapadn_Adapter.Editor { public sealed class TapadnBuildAndroidProcess : IPostGenerateGradleAndroidProject { private static readonly XNamespace AndroidNamespace = "http://schemas.android.com/apk/res/android"; private static readonly XNamespace ToolsNamespace = "http://schemas.android.com/tools"; public int callbackOrder => 180; public void OnPostGenerateGradleAndroidProject(string path) { ProcessGradleProperties(path); ProcessAndroidManifest(path); } public static void ProcessAndroidManifest(string path) { var unityManifestPath = Path.Combine(path, "src/main/AndroidManifest.xml"); var launcherManifestPath = Path.Combine(path, "../launcher/src/main/AndroidManifest.xml"); var providerTargetManifestPath = File.Exists(launcherManifestPath) ? launcherManifestPath : unityManifestPath; if (!File.Exists(unityManifestPath)) { Debug.LogWarning($"[TapADN] AndroidManifest.xml is missing: {unityManifestPath}"); return; } var unityManifest = XDocument.Load(unityManifestPath); EnsureManifestPatch(unityManifest, true, false); unityManifest.Save(unityManifestPath); if (File.Exists(providerTargetManifestPath)) { var providerManifest = XDocument.Load(providerTargetManifestPath); EnsureManifestPatch(providerManifest, false, true); providerManifest.Save(providerTargetManifestPath); } var resXmlPath = Path.Combine(path, "src/main/res/xml"); Directory.CreateDirectory(resXmlPath); var sourceXmlPath = GetEditorAssetPath("tapad_ad_file_path.xml"); if (!string.IsNullOrEmpty(sourceXmlPath) && File.Exists(sourceXmlPath)) { File.Copy(sourceXmlPath, Path.Combine(resXmlPath, "tapad_ad_file_path.xml"), true); } } private static void EnsureManifestPatch(XDocument manifest, bool includeWechatActivity, bool includeTapadProvider) { var manifestElement = manifest.Element("manifest"); if (manifestElement == null) { return; } EnsureNamespace(manifestElement, "tools", ToolsNamespace.NamespaceName); EnsurePermission(manifestElement, "android.permission.INTERNET"); EnsurePermission(manifestElement, "android.permission.ACCESS_NETWORK_STATE"); EnsurePermission(manifestElement, "android.permission.READ_PHONE_STATE"); EnsurePermission(manifestElement, "android.permission.QUERY_ALL_PACKAGES"); EnsurePermission(manifestElement, "android.permission.REQUEST_INSTALL_PACKAGES"); EnsurePermission(manifestElement, "android.permission.BLUETOOTH"); EnsurePermission(manifestElement, "android.permission.BLUETOOTH_CONNECT"); EnsurePermission(manifestElement, "android.permission.ACCESS_FINE_LOCATION"); EnsurePermission(manifestElement, "android.permission.ACCESS_COARSE_LOCATION"); EnsurePermission(manifestElement, "android.permission.POST_NOTIFICATIONS"); EnsureWechatQueries(manifestElement); var applicationElement = manifestElement.Element("application"); if (applicationElement == null) { applicationElement = new XElement("application"); manifestElement.Add(applicationElement); } SetUnityActivitySingleTop(applicationElement); if (includeWechatActivity) { EnsureWechatEntryActivity(applicationElement); } if (includeTapadProvider) { EnsureTapadFileProvider(applicationElement); SetOrAddAttribute(applicationElement, AndroidNamespace + "allowBackup", "false"); MergeToolsReplaceAttribute(applicationElement, "android:allowBackup"); } } private static void EnsurePermission(XElement manifestElement, string permissionName) { var exists = manifestElement.Elements("uses-permission") .Any(element => element.Attribute(AndroidNamespace + "name")?.Value == permissionName); if (exists) { return; } var permission = new XElement("uses-permission"); permission.Add(new XAttribute(AndroidNamespace + "name", permissionName)); if (permissionName == "android.permission.BLUETOOTH_CONNECT" || permissionName == "android.permission.POST_NOTIFICATIONS") { permission.Add(new XAttribute(ToolsNamespace + "targetApi", permissionName.EndsWith("CONNECT") ? "s" : "33")); } manifestElement.Add(permission); } private static void EnsureWechatQueries(XElement manifestElement) { var queries = manifestElement.Elements("queries").FirstOrDefault(); if (queries == null) { queries = new XElement("queries"); manifestElement.Add(queries); } var exists = queries.Elements("package") .Any(element => element.Attribute(AndroidNamespace + "name")?.Value == "com.tencent.mm"); if (!exists) { var packageElement = new XElement("package"); packageElement.Add(new XAttribute(AndroidNamespace + "name", "com.tencent.mm")); queries.Add(packageElement); } } private static void EnsureWechatEntryActivity(XElement applicationElement) { RemoveElementsByAndroidName(applicationElement, "activity", ".wxapi.WXEntryActivity"); var activity = new XElement("activity"); activity.Add(new XAttribute(AndroidNamespace + "name", ".wxapi.WXEntryActivity")); activity.Add(new XAttribute(AndroidNamespace + "label", "@string/app_name")); activity.Add(new XAttribute(AndroidNamespace + "theme", "@android:style/Theme.Translucent.NoTitleBar")); activity.Add(new XAttribute(AndroidNamespace + "exported", "true")); activity.Add(new XAttribute(AndroidNamespace + "taskAffinity", Application.identifier)); activity.Add(new XAttribute(AndroidNamespace + "launchMode", "singleTop")); applicationElement.Add(activity); } private static void EnsureTapadFileProvider(XElement applicationElement) { RemoveElementsByAndroidName(applicationElement, "provider", "com.tapsdk.tapad.internal.TapADFileProvider"); var provider = new XElement("provider"); provider.Add(new XAttribute(AndroidNamespace + "name", "com.tapsdk.tapad.internal.TapADFileProvider")); provider.Add(new XAttribute(AndroidNamespace + "authorities", "${applicationId}.com.tds.ad.fileprovider")); provider.Add(new XAttribute(AndroidNamespace + "exported", "false")); provider.Add(new XAttribute(AndroidNamespace + "grantUriPermissions", "true")); provider.Add(new XAttribute(ToolsNamespace + "replace", "android:authorities")); var metadata = new XElement("meta-data"); metadata.Add(new XAttribute(AndroidNamespace + "name", "android.support.FILE_PROVIDER_PATHS")); metadata.Add(new XAttribute(AndroidNamespace + "resource", "@xml/tapad_ad_file_path")); provider.Add(metadata); applicationElement.Add(provider); } private static void SetUnityActivitySingleTop(XElement applicationElement) { foreach (var activity in applicationElement.Elements("activity")) { var name = activity.Attribute(AndroidNamespace + "name")?.Value; if (name == "com.unity3d.player.UnityPlayerActivity") { SetOrAddAttribute(activity, AndroidNamespace + "launchMode", "singleTop"); } } } private static void RemoveElementsByAndroidName(XElement parent, string localName, string androidName) { parent.Elements(localName) .Where(element => element.Attribute(AndroidNamespace + "name")?.Value == androidName) .ToList() .ForEach(element => element.Remove()); } private static void ProcessGradleProperties(string path) { var root = Directory.GetParent(path)?.FullName; if (string.IsNullOrEmpty(root)) { return; } var gradlePropertiesPath = Path.Combine(root, "gradle.properties"); var lines = File.Exists(gradlePropertiesPath) ? File.ReadAllLines(gradlePropertiesPath).ToList() : new List(); UpsertProperty(lines, "android.useAndroidX", "true"); UpsertProperty(lines, "android.enableJetifier", "true"); File.WriteAllText(gradlePropertiesPath, string.Join("\n", lines) + "\n"); } private static void UpsertProperty(List lines, string key, string value) { var prefix = key + "="; var index = lines.FindIndex(line => line.TrimStart().StartsWith(prefix)); if (index >= 0) { lines[index] = prefix + value; return; } lines.Add(prefix + value); } private static void SetOrAddAttribute(XElement element, XName attributeName, string value) { var attribute = element.Attribute(attributeName); if (attribute == null) { element.Add(new XAttribute(attributeName, value)); return; } attribute.SetValue(value); } private static void MergeToolsReplaceAttribute(XElement element, params string[] replaceValues) { var replaceAttributeName = ToolsNamespace + "replace"; var mergedValues = new List(); var currentValue = element.Attribute(replaceAttributeName)?.Value; if (!string.IsNullOrWhiteSpace(currentValue)) { mergedValues.AddRange(currentValue.Split(',') .Select(value => value.Trim()) .Where(value => !string.IsNullOrWhiteSpace(value))); } foreach (var replaceValue in replaceValues) { if (!mergedValues.Contains(replaceValue)) { mergedValues.Add(replaceValue); } } element.SetAttributeValue(replaceAttributeName, string.Join(",", mergedValues)); } private static void EnsureNamespace(XElement element, string prefix, string namespaceName) { var namespaceAttributeName = XNamespace.Xmlns + prefix; if (element.Attribute(namespaceAttributeName) == null) { element.Add(new XAttribute(namespaceAttributeName, namespaceName)); } } private static string GetEditorAssetPath(string fileName) { var guids = AssetDatabase.FindAssets(Path.GetFileNameWithoutExtension(fileName)); foreach (var guid in guids) { var path = AssetDatabase.GUIDToAssetPath(guid); if (Path.GetFileName(path) == fileName) { return path; } } return null; } } } #endif