Example #1
0
 private static void SetAttributionReportEndpoint(PlistDocument plist, CASEditorSettings settings)
 {
     if (!string.IsNullOrEmpty(settings.attributionReportEndpoint))
     {
         plist.root.SetString("NSAdvertisingAttributionReportEndpoint", settings.attributionReportEndpoint);
     }
 }
Example #2
0
        public static void OnCocoaPodsReady(BuildTarget buildTarget, string buildPath)
        {
            if (buildTarget != BuildTarget.iOS)
            {
                return;
            }

            var editorSettings            = CASEditorSettings.Load();
            var needLocalizeUserTracking  = IsNeedLocalizeUserTrackingDescription(editorSettings);
            var needEmbedDynamicLibraries = IsNeedEmbedDynamicLibraries();

            if (!needEmbedDynamicLibraries && !needLocalizeUserTracking)
            {
                return;
            }

            var    project = OpenXCode(buildPath);
            string mainTargetGuid;
            string frameworkTargetGuid;

            GetTargetsGUID(project, out mainTargetGuid, out frameworkTargetGuid);
            if (needLocalizeUserTracking)
            {
                LocalizeUserTrackingDescription(buildPath, project, mainTargetGuid, editorSettings);
            }

            if (needEmbedDynamicLibraries)
            {
                var depManager = DependencyManager.Create(BuildTarget.iOS, Audience.Mixed, true);
                EmbedDynamicLibrariesIfNeeded(buildPath, project, mainTargetGuid, depManager);
            }

            SaveXCode(project, buildPath);
        }
Example #3
0
        private static void SetDefaultUserTrackingDescription(PlistDocument plist, CASEditorSettings settings)
        {
            if (settings.userTrackingUsageDescription.Length == 0)
            {
                return;
            }
            var description = settings.userTrackingUsageDescription[0].value;

            if (string.IsNullOrEmpty(description))
            {
                return;
            }
            plist.root.SetString("NSUserTrackingUsageDescription", description);
        }
Example #4
0
        [PostProcessBuild(47)]  //must be between 40 and 50 to ensure that it's not overriden by Podfile generation (40) and that it's added before "pod install" (50)
        public static void MainPostprocess(BuildTarget target, string buildPath)
        {
            if (target != BuildTarget.iOS)
            {
                return;
            }

            // Init Settings can be null
            var initSettings   = CASEditorUtils.GetSettingsAsset(BuildTarget.iOS, false);
            var editorSettings = CASEditorSettings.Load();
            var depManager     = DependencyManager.Create(BuildTarget.iOS, Audience.Mixed, true);

            string        plistPath = Path.Combine(buildPath, "Info.plist");
            PlistDocument plist     = new PlistDocument();

            plist.ReadFromFile(plistPath);

            UpdateGADDelayMeasurement(plist, editorSettings);
            UpdateGADAppId(plist, initSettings, depManager);
            UpdateSKAdNetworksInfo(plist);
            UpdateLSApplicationQueriesSchames(plist);
            UpdateAppTransportSecuritySettings(plist);
            SetAttributionReportEndpoint(plist, editorSettings);
            SetDefaultUserTrackingDescription(plist, editorSettings);

            File.WriteAllText(plistPath, plist.WriteToString());

            var    project = OpenXCode(buildPath);
            string mainTargetGuid;
            string frameworkTargetGuid;

            GetTargetsGUID(project, out mainTargetGuid, out frameworkTargetGuid);

            EnableSwiftLibraries(buildPath, project, mainTargetGuid, frameworkTargetGuid);
            CopyRawSettingsFile(buildPath, project, mainTargetGuid, initSettings);
            SetExecutablePath(buildPath, project, mainTargetGuid, depManager);

            SaveXCode(project, buildPath);

            if (editorSettings.generateIOSDeepLinksForPromo)
            {
                ApplyCrosspromoDynamicLinks(buildPath, mainTargetGuid, initSettings, depManager);
            }

#if UNITY_2019_3_OR_NEWER
            UpdatePodfileForUnity2019(buildPath);
#endif
            Debug.Log(CASEditorUtils.logTag + "Postrocess Build done.");
        }
 public static void ConfigureProjectForTargetPlatform()
 {
     try
     {
         var target = EditorUserBuildSettings.activeBuildTarget;
         CASPreprocessBuild.ConfigureProject(target, CASEditorSettings.Load());
         if (target == BuildTarget.Android)
         {
             TryResolveAndroidDependencies();
         }
         EditorUtility.ClearProgressBar();
         EditorUtility.DisplayDialog("Configure project",
                                     "CAS Plugin has successfully applied all required configurations to your project.",
                                     "Ok");
     }
     finally
     {
         EditorUtility.ClearProgressBar();
     }
 }
        public static string GetNewVersionOrNull(string repo, string currVersion, bool force)
        {
            try
            {
                string newVerStr = null;
                if (!force)
                {
                    var editorSettings = CASEditorSettings.Load();
                    if (!editorSettings.autoCheckForUpdatesEnabled)
                    {
                        return(null);
                    }

                    if (!HasTimePassed(editorLatestVersionTimestampPrefs + repo, 1, false))
                    {
                        newVerStr = EditorPrefs.GetString(editorLatestVersionPrefs + repo);
                    }
                }

                if (string.IsNullOrEmpty(newVerStr))
                {
                    newVerStr = GetLatestVersion(repo, currVersion);
                }
                if (newVerStr != null && newVerStr != currVersion && !currVersion.Contains("-RC"))
                {
                    var currVer = new System.Version(currVersion);
                    var newVer  = new System.Version(newVerStr);
                    if (currVer < newVer)
                    {
                        return(newVerStr);
                    }
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
            return(null);
        }
        public void OnPreprocessBuild(BuildTarget target, string path)
        {
#endif
            if (target != BuildTarget.Android && target != BuildTarget.iOS)
            {
                return;
            }
            try
            {
                var editorSettings = CASEditorSettings.Load();
                if (editorSettings.buildPreprocessEnabled)
                {
                    ConfigureProject(target, editorSettings);
                    EditorUtility.DisplayProgressBar("Hold on", "Prepare components...", 0.95f);
                }
            }
            catch (Exception e)
            {
                // If no errors are found then there is no need to clear the progress for the user.
                EditorUtility.ClearProgressBar();
                throw e;
            }
        }
Example #8
0
        private void InitEditorSettingsProperties()
        {
            editorSettingsObj = new SerializedObject(CASEditorSettings.Load(true));
            autoCheckForUpdatesEnabledProp = editorSettingsObj.FindProperty("autoCheckForUpdatesEnabled");
            delayAppMeasurementGADInitProp = editorSettingsObj.FindProperty("delayAppMeasurementGADInit");
            buildPreprocessEnabledProp     = editorSettingsObj.FindProperty("buildPreprocessEnabled");
            updateGradlePluginVersionProp  = editorSettingsObj.FindProperty("updateGradlePluginVersion");
            multiDexEnabledProp            = editorSettingsObj.FindProperty("multiDexEnabled");
            exoPlayerIncludedProp          = editorSettingsObj.FindProperty("exoPlayerIncluded");
            permissionAdIdRemovedProp      = editorSettingsObj.FindProperty("permissionAdIdRemoved");

            mostPopularCountryOfUsersProp = editorSettingsObj.FindProperty("mostPopularCountryOfUsers");
            attributionReportEndpointProp = editorSettingsObj.FindProperty("attributionReportEndpoint");

            userTrackingUsageDescriptionProp = editorSettingsObj.FindProperty("userTrackingUsageDescription");

            userTrackingList = new ReorderableList(editorSettingsObj, userTrackingUsageDescriptionProp, true, true, true, true)
            {
                drawHeaderCallback  = DrawNSTrackingListHeader,
                drawElementCallback = DrawNSTrackingListElement,
                onCanRemoveCallback = DisabledRemoveLastItemFromList,
            };
        }
        private static void ConfigureAndroid(CASInitSettings settings, CASEditorSettings editorSettings, string admobAppId)
        {
#if UNITY_ANDROID || CASDeveloper
            EditorUtility.DisplayProgressBar(casTitle, "Validate CAS Android Build Settings", 0.8f);

            const string deprecatedPluginPath = "Assets/Plugins/CAS";
            if (AssetDatabase.IsValidFolder(deprecatedPluginPath))
            {
                AssetDatabase.DeleteAsset(deprecatedPluginPath);
                Debug.Log("Removed deprecated plugin: " + deprecatedPluginPath);
            }

            HashSet <string> promoAlias = new HashSet <string>();
            if (editorSettings.generateAndroidQuerriesForPromo)
            {
                for (int i = 0; i < settings.managersCount; i++)
                {
                    Utils.GetCrossPromoAlias(BuildTarget.Android, settings.GetManagerId(i), promoAlias);
                }
            }

            UpdateAndroidPluginManifest(admobAppId, promoAlias, editorSettings);

            CASPreprocessGradle.Configure(editorSettings);

#if !UNITY_2021_2_OR_NEWER
            // 19 - AndroidSdkVersions.AndroidApiLevel19
            // Deprecated in Unity 2021.2
            if (PlayerSettings.Android.minSdkVersion < ( AndroidSdkVersions )19)
            {
                Utils.DialogOrCancelBuild("CAS required a higher minimum SDK API level. Set SDK level 19 (KitKat) and continue?", BuildTarget.NoTarget);
                PlayerSettings.Android.minSdkVersion = ( AndroidSdkVersions )19;
            }
#endif
#endif
        }
Example #10
0
 private static bool IsNeedLocalizeUserTrackingDescription(CASEditorSettings settings)
 {
     return(settings.userTrackingUsageDescription.Length > 1);
 }
Example #11
0
 private static void UpdateGADDelayMeasurement(PlistDocument plist, CASEditorSettings editorSettings)
 {
     plist.root.SetBoolean("GADDelayAppMeasurementInit", editorSettings.delayAppMeasurementGADInit);
 }
Example #12
0
        private static void LocalizeUserTrackingDescription(string buildPath, PBXProject project, string targetGuid, CASEditorSettings settings)
        {
            const string LegacyResourcesDirectoryName = "Resources";
            const string CASResourcesDirectoryName    = "CASUResources";

            if (settings.userTrackingUsageDescription.Length < 2)
            {
                return;
            }

            // 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 : CASResourcesDirectoryName;
            var resourcesDirectoryPath = Path.Combine(buildPath, resourcesDirectoryName);

            for (int i = 0; i < settings.userTrackingUsageDescription.Length; i++)
            {
                var keyValue    = settings.userTrackingUsageDescription[i];
                var description = keyValue.value;
                var localeCode  = keyValue.key;
                if (string.IsNullOrEmpty(localeCode))
                {
                    continue;
                }
                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 (string.IsNullOrEmpty(description))
                {
                    if (File.Exists(infoPlistStringsFilePath))
                    {
                        File.Delete(infoPlistStringsFilePath);
                    }
                    continue;
                }

                // Create intermediate directories as needed.
                if (!Directory.Exists(resourcesDirectoryPath))
                {
                    Directory.CreateDirectory(resourcesDirectoryPath);
                }
                if (!Directory.Exists(localeSpecificDirectoryPath))
                {
                    Directory.CreateDirectory(localeSpecificDirectoryPath);
                }


                var localizedDescriptionLine = "\"NSUserTrackingUsageDescription\" = \"" + description + "\";\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 CAS plugin */\n" + localizedDescriptionLine);
                }

                var guid = project.AddFolderReference(localeSpecificDirectoryPath, Path.Combine(resourcesDirectoryName, localeSpecificDirectoryName));
                project.AddFileToBuild(targetGuid, guid);
            }
        }
 public static bool isUseAdvertiserIdLimited()
 {
     return(CASEditorSettings.Load().permissionAdIdRemoved);
 }
        public static void ConfigureProject(BuildTarget target, CASEditorSettings editorSettings)
        {
            if (target != BuildTarget.Android && target != BuildTarget.iOS)
            {
                return;
            }

            var settings = Utils.GetSettingsAsset(target, false);

            if (!settings)
            {
                Utils.StopBuildWithMessage("Settings asset not found. Please use menu Assets > CleverAdsSolutions > Settings " +
                                           "to create and set settings for build.", target);
            }

            var deps = DependencyManager.Create(target, Audience.Mixed, true);

            if (!Application.isBatchMode)
            {
                var newCASVersion = Utils.GetNewVersionOrNull(Utils.gitUnityRepo, MobileAds.wrapperVersion, false);
                if (newCASVersion != null)
                {
                    Utils.DialogOrCancelBuild("There is a new version " + newCASVersion + " of the CAS Unity available for update.", target);
                }

                if (deps != null)
                {
                    if (!deps.installedAny)
                    {
                        Utils.StopBuildWithMessage("Dependencies of native SDK were not found. " +
                                                   "Please use 'Assets > CleverAdsSolutions > Settings' menu to integrate solutions or any SDK separately.", target);
                    }

                    if (deps.IsNewerVersionFound())
                    {
                        Utils.DialogOrCancelBuild("There is a new versions of the native dependencies available for update." +
                                                  "Please use 'Assets > CleverAdsSolutions >Settings' menu to update.", target);
                    }
                }
            }

            if (settings.managersCount == 0 || string.IsNullOrEmpty(settings.GetManagerId(0)))
            {
                StopBuildIDNotFound(target);
            }

            string admobAppId = UpdateRemoteSettingsAndGetAppId(settings, target, deps);

            if (target == BuildTarget.Android)
            {
                ConfigureAndroid(settings, editorSettings, admobAppId);
            }
            else if (target == BuildTarget.iOS)
            {
                ConfigureIOS();
            }

#pragma warning disable CS0618 // Type or member is obsolete
            // Use directrly property to avoid Debug build
            if (settings.testAdMode && !EditorUserBuildSettings.development)
            {
                Debug.LogWarning(Utils.logTag + "Test Ads Mode enabled! Make sure the build is for testing purposes only!\n" +
                                 "Use 'Assets > CleverAdsSolutions > Settings' menu to disable Test Ad Mode.");
            }
            else
            {
                Debug.Log(Utils.logTag + "Project configuration completed");
            }
#pragma warning restore CS0618 // Type or member is obsolete
        }
        internal static void Configure(CASEditorSettings settings)
        {
            bool          baseGradleChanged = false;
            List <string> baseGradle        = null;
            List <string> launcherGradle    = null;

#if UNITY_2019_3_OR_NEWER
            const string baseGradlePath     = Utils.projectGradlePath;
            const string launcherGradlePath = Utils.launcherGradlePath;

            baseGradle = ReadGradleFile("Base Gradle", baseGradlePath);

            if (settings.multiDexEnabled || settings.exoPlayerIncluded)
            {
                launcherGradle = ReadGradleFile("Launcher Gradle", launcherGradlePath);
            }
#else
            const string baseGradlePath     = Utils.mainGradlePath;
            const string launcherGradlePath = Utils.mainGradlePath;

            baseGradle     = ReadGradleFile("Gradle", baseGradlePath);
            launcherGradle = baseGradle;
#endif

            if (settings.updateGradlePluginVersion &&
                UpdateGradlePluginVersion(baseGradle, baseGradlePath))
            {
                baseGradleChanged = true;
            }

#if ReplaceJCenterToMavenCentral
            if (UpdateBaseGradleRepositories(baseGradle, baseGradlePath))
            {
                baseGradleChanged = true;
            }
#endif

            // Enabled by default Dexing artifact transform causes issues for ExoPlayer with Gradle plugin 3.5.0+
            var dexingArtifactProp = new GradleProperty(
                "android.enableDexingArtifactTransform", "false", !settings.exoPlayerIncluded);

            GradleProperty[] gradleProps = null;
            if (Utils.GetAndroidResolverSetting <bool>("UseJetifier"))
            {
                gradleProps = new[] { dexingArtifactProp };
            }
            else
            {
                gradleProps = new[]
                {
                    dexingArtifactProp,
                    new GradleProperty("android.useAndroidX", "true"),
                    new GradleProperty("android.enableJetifier", "true")
                };
            }

#if UNITY_2019_3_OR_NEWER
            List <string> propsFile = ReadGradleFile("Gradle Properties", Utils.propertiesGradlePath);

            if (UpdateGradlePropertiesFile(propsFile, gradleProps))
            {
                File.WriteAllLines(Utils.propertiesGradlePath, propsFile.ToArray());
            }
#else
            // Unity below version 2019.3 does not have a Gradle Properties file
            // and changes are applied to the base Gradle file.
            if (UpdateGradlePropertiesInMainFile(baseGradle, gradleProps, baseGradlePath))
            {
                baseGradleChanged = true;
            }


            if (FixGradleCompatibilityUnity2018(baseGradle, baseGradlePath))
            {
                baseGradleChanged = true;
            }
#endif

            if (launcherGradle != null)
            {
                if (UpdateLauncherGradleFile(launcherGradle, settings, launcherGradlePath))
                {
#if UNITY_2019_3_OR_NEWER
                    File.WriteAllLines(launcherGradlePath, launcherGradle.ToArray());
                    AssetDatabase.ImportAsset(launcherGradlePath);
#else
                    // Unity below version 2019.3 does not have a Gradle Launcher file
                    // and changes are applied to the base Gradle file.
                    baseGradleChanged = true;
#endif
                }
            }

            if (baseGradleChanged)
            {
                File.WriteAllLines(baseGradlePath, baseGradle.ToArray());
                AssetDatabase.ImportAsset(baseGradlePath);
            }
        }
        internal static string DownloadRemoteSettings(string managerID, BuildTarget platform, CASInitSettings settings, DependencyManager deps)
        {
            const string title = "Update CAS remote settings";

            var editorSettings = CASEditorSettings.Load();

            #region Create request URL
            #region Hash
            var managerIdBytes = new UTF8Encoding().GetBytes(managerID);
            var suffix         = new byte[] { 48, 77, 101, 68, 105, 65, 116, 73, 111, 78, 104, 65, 115, 72 };
            if (platform == BuildTarget.iOS)
            {
                suffix[0] = 49;
            }
            var sourceBytes = new byte[managerID.Length + suffix.Length];
            Array.Copy(managerIdBytes, 0, sourceBytes, 0, managerIdBytes.Length);
            Array.Copy(suffix, 0, sourceBytes, managerIdBytes.Length, suffix.Length);

            var           hashBytes   = new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(sourceBytes);
            StringBuilder hashBuilder = new StringBuilder();
            for (int i = 0; i < hashBytes.Length; i++)
            {
                hashBuilder.Append(Convert.ToString(hashBytes[i], 16).PadLeft(2, '0'));
            }
            var hash = hashBuilder.ToString().PadLeft(32, '0');
            #endregion

            var urlBuilder = new StringBuilder("https://psvpromo.psvgamestudio.com/Scr/cas.php?platform=")
                             .Append(platform == BuildTarget.Android ? 0 : 1)
                             .Append("&bundle=").Append(UnityWebRequest.EscapeURL(managerID))
                             .Append("&hash=").Append(hash)
                             .Append("&lang=").Append(SystemLanguage.English)
                             .Append("&appDev=2")
                             .Append("&appV=").Append(PlayerSettings.bundleVersion)
                             .Append("&coppa=").Append(( int )settings.defaultAudienceTagged)
                             .Append("&adTypes=").Append(( int )settings.allowedAdFlags)
                             .Append("&nets=").Append(DependencyManager.GetActiveMediationPattern(deps))
                             .Append("&orient=").Append(Utils.GetOrientationId())
                             .Append("&framework=Unity_").Append(Application.unityVersion);
            if (deps != null)
            {
                var buildCode = deps.GetInstalledBuildCode();
                if (buildCode > 0)
                {
                    urlBuilder.Append("&sdk=").Append(buildCode);
                }
            }
            if (string.IsNullOrEmpty(editorSettings.mostPopularCountryOfUsers))
            {
                urlBuilder.Append("&country=").Append("US");
            }
            else
            {
                urlBuilder.Append("&country=").Append(editorSettings.mostPopularCountryOfUsers);
            }
            if (platform == BuildTarget.Android)
            {
                urlBuilder.Append("&appVC=").Append(PlayerSettings.Android.bundleVersionCode);
            }

            #endregion

            using (var loader = UnityWebRequest.Get(urlBuilder.ToString()))
            {
                try
                {
                    loader.SendWebRequest();
                    while (!loader.isDone)
                    {
                        if (EditorUtility.DisplayCancelableProgressBar(title, managerID,
                                                                       Mathf.Repeat(( float )EditorApplication.timeSinceStartup * 0.2f, 1.0f)))
                        {
                            loader.Dispose();
                            throw new Exception("Update CAS Settings canceled");
                        }
                    }
                    if (string.IsNullOrEmpty(loader.error))
                    {
                        var content = loader.downloadHandler.text.Trim();
                        if (string.IsNullOrEmpty(content))
                        {
                            throw new Exception("ManagerID [" + managerID + "] is not registered in CAS.");
                        }

                        EditorUtility.DisplayProgressBar(title, "Write CAS settings", 0.7f);
                        var data = JsonUtility.FromJson <AdmobAppIdData>(content);
                        Utils.WriteToFile(content, Utils.GetNativeSettingsPath(platform, managerID));
                        return(data.admob_app_id);
                    }
                    throw new Exception("Server response " + loader.responseCode + ": " + loader.error);
                }
                finally
                {
                    EditorUtility.ClearProgressBar();
                }
            }
        }
        private static void UpdateAndroidPluginManifest(string admobAppId, HashSet <string> queries, CASEditorSettings settings)
        {
            const string metaAdmobApplicationID = "com.google.android.gms.ads.APPLICATION_ID";
            const string metaAdmobDelayInit     = "com.google.android.gms.ads.DELAY_APP_MEASUREMENT_INIT";

            XNamespace ns             = "http://schemas.android.com/apk/res/android";
            XNamespace nsTools        = "http://schemas.android.com/tools";
            XName      nameAttribute  = ns + "name";
            XName      valueAttribute = ns + "value";

            string manifestPath = Path.GetFullPath(Utils.androidLibManifestPath);

            CreateAndroidLibIfNedded();

            if (string.IsNullOrEmpty(admobAppId))
            {
                admobAppId = Utils.androidAdmobSampleAppID;
            }

            try
            {
                var document = new XDocument(
                    new XDeclaration("1.0", "utf-8", null),
                    new XComment("This file is automatically generated by CAS Unity plugin from `Assets > CleverAdsSolutions > Android Settings`"),
                    new XComment("Do not modify this file. YOUR CHANGES WILL BE ERASED!"));
                var elemManifest = new XElement("manifest",
                                                new XAttribute(XNamespace.Xmlns + "android", ns),
                                                new XAttribute(XNamespace.Xmlns + "tools", nsTools),
                                                new XAttribute("package", "com.cleversolutions.ads.unitycas"),
                                                new XAttribute(ns + "versionName", MobileAds.wrapperVersion),
                                                new XAttribute(ns + "versionCode", 1));
                document.Add(elemManifest);

                var delayInitState = settings.delayAppMeasurementGADInit ? "true" : "false";

                var elemApplication = new XElement("application");

                var elemAppIdMeta = new XElement("meta-data",
                                                 new XAttribute(nameAttribute, metaAdmobApplicationID),
                                                 new XAttribute(valueAttribute, admobAppId));
                elemApplication.Add(elemAppIdMeta);

                var elemDelayInitMeta = new XElement("meta-data",
                                                     new XAttribute(nameAttribute, metaAdmobDelayInit),
                                                     new XAttribute(valueAttribute, delayInitState));
                elemApplication.Add(elemDelayInitMeta);

                var elemUsesLibrary = new XElement("uses-library",
                                                   new XAttribute(ns + "required", "false"),
                                                   new XAttribute(nameAttribute, "org.apache.http.legacy"));
                elemApplication.Add(elemUsesLibrary);
                elemManifest.Add(elemApplication);

                var elemInternetPermission = new XElement("uses-permission",
                                                          new XAttribute(nameAttribute, "android.permission.INTERNET"));
                elemManifest.Add(elemInternetPermission);

                var elemNetworkPermission = new XElement("uses-permission",
                                                         new XAttribute(nameAttribute, "android.permission.ACCESS_NETWORK_STATE"));
                elemManifest.Add(elemNetworkPermission);

                var elemWIFIPermission = new XElement("uses-permission",
                                                      new XAttribute(nameAttribute, "android.permission.ACCESS_WIFI_STATE"));
                elemManifest.Add(elemWIFIPermission);

                var elemAdIDPermission = new XElement("uses-permission",
                                                      new XAttribute(nameAttribute, "com.google.android.gms.permission.AD_ID"));
                if (settings.permissionAdIdRemoved)
                {
                    elemAdIDPermission.SetAttributeValue(nsTools + "node", "remove");
                }
                elemManifest.Add(elemAdIDPermission);

                if (queries.Count > 0)
                {
                    var elemQueries = new XElement("queries");
                    elemQueries.Add(new XComment("CAS Cross promotion"));
                    foreach (var item in queries)
                    {
                        elemQueries.Add(new XElement("package",
                                                     new XAttribute(nameAttribute, item)));
                    }
                    elemManifest.Add(elemQueries);
                }

                var exist = File.Exists(Utils.androidLibManifestPath);
                // XDocument required absolute path
                document.Save(manifestPath);
                // But Unity not support absolute path
                if (!exist)
                {
                    AssetDatabase.ImportAsset(Utils.androidLibManifestPath);
                }
            }
            catch (Exception e)
            {
                Debug.LogException(e);
            }
        }
        private static bool UpdateLauncherGradleFile(List <string> gradle, CASEditorSettings settings, string filePath)
        {
            bool isChanged = false;
            int  line      = 0;
            bool required  = settings.multiDexEnabled || settings.exoPlayerIncluded;

            // Find dependencies{} scope
            do
            {
                ++line;
                if (line >= gradle.Count)
                {
                    if (required)
                    {
                        LogWhenGradleLineNotFound("dependencies{} scope", filePath);
                    }
                    return(isChanged);
                }
            } while (!gradle[line].Contains("implementation"));

            // Find Multidex dependency in scope
            const string depPrefix              = "    implementation '";
            bool         multidexExist          = false;
            const string multidexAndroidSupport = "com.android.support:multidex:";
            const string multidexAndroidX       = "androidx.multidex:multidex:";
            const string miltidexAndroidXLine   = depPrefix + multidexAndroidX + "2.0.1' // Added by CAS settings";

            bool         exoPlayerExist = false;
            const string exoPlayerDep   = "com.google.android.exoplayer:exoplayer:";
            const string exoPlayerLine  = depPrefix + exoPlayerDep + "2.13.3' // Added by CAS settings";

            do
            {
                ++line;
                if (line >= gradle.Count)
                {
                    if (required)
                    {
                        LogWhenGradleLineNotFound("dependencies{} scope", filePath);
                    }
                    return(isChanged);
                }
                var removeLine = false;
                if (gradle[line].Contains(multidexAndroidSupport))
                {
                    removeLine = multidexExist || !settings.multiDexEnabled;
                    if (!removeLine)
                    {
                        gradle[line] = miltidexAndroidXLine;
                        Debug.Log(Utils.logTag + "Updated " + multidexAndroidSupport +
                                  " to " + multidexAndroidX + " in " + filePath + Utils.logAutoFeature);
                        isChanged = true;
                    }
                    multidexExist = true;
                }
                else if (gradle[line].Contains(multidexAndroidX))
                {
                    removeLine    = multidexExist || !settings.multiDexEnabled;
                    multidexExist = true;
                }
                else if (gradle[line].Contains(exoPlayerDep))
                {
                    removeLine     = exoPlayerExist || !settings.exoPlayerIncluded;
                    exoPlayerExist = true;
                }
                if (removeLine)
                {
                    Debug.Log(Utils.logTag + "Removed: '" + gradle[line] + "' from: " + filePath);
                    gradle.RemoveAt(line);
                    --line;
                    isChanged = true;
                }
            } while (!gradle[line].Contains('}'));

            if (!multidexExist && settings.multiDexEnabled)
            {
                gradle.Insert(line, miltidexAndroidXLine);
                Debug.Log(Utils.logTag + "Appended " + multidexAndroidX + " to " + filePath + Utils.logAutoFeature);
                multidexExist = true;
                isChanged     = true;
                ++line;
            }

            if (!exoPlayerExist && settings.exoPlayerIncluded)
            {
                gradle.Insert(line, exoPlayerLine);
                Debug.Log(Utils.logTag + "Appended " + exoPlayerDep + " to " + filePath + Utils.logAutoFeature);
                exoPlayerExist = true;
                isChanged      = true;
                ++line;
            }

#if DeclareJavaVersion
            const string javaVersion          = "JavaVersion.VERSION_1_8";
            var          existJavaDeclaration = false;
#endif

#if ExcludeAndroidxAnnotations
            const string excludeOption     = "exclude 'META-INF/proguard/androidx-annotations.pro'";
            var          packagingOptExist = false;
#endif

            required = settings.multiDexEnabled;
            do // while defaultConfig scope
            {
                ++line;
                if (line >= gradle.Count)
                {
                    if (required)
                    {
                        LogWhenGradleLineNotFound("defaultConfig{} scope", filePath);
                    }
                    return(isChanged);
                }
#if DeclareJavaVersion
                if (!existJavaDeclaration && gradle[line].Contains(javaVersion))
                {
                    existJavaDeclaration = true;
                }
#endif
#if ExcludeAndroidxAnnotations
                if (!packagingOptExist && gradle[line].Contains(excludeOption))
                {
                    packagingOptExist = true;
                }
#endif
            } while (!gradle[line].Contains("defaultConfig"));

#if DeclareJavaVersion
            if (!existJavaDeclaration)
            {
                var compileOptions = new[] {
                    "	compileOptions {",
                    "        sourceCompatibility " + javaVersion,
                    "        targetCompatibility " + javaVersion,
                    "	}",
                    ""
                };
                gradle.InsertRange(line, compileOptions);
                line     += compileOptions.Length;
                isChanged = true;
                Debug.Log(Utils.logTag + "Appended Compile options to use Java Version 1.8 in " + filePath + Utils.logAutoFeature);
            }
#endif

#if ExcludeAndroidxAnnotations
            if (!packagingOptExist)
            {
                var packagingOptions = new[] {
                    "	packagingOptions {",
                    "        " + excludeOption,
                    "	}",
                    ""
                };
                gradle.InsertRange(line, packagingOptions);
                line     += packagingOptions.Length;
                isChanged = true;
                Debug.Log(Utils.logTag + "Appended Packaging options to exclude duplicate androidx-annotations. " + filePath + Utils.logAutoFeature);
            }
#endif

            // Find multidexEnable in defaultConfig{} scope
            const string multidexConfig = "multiDexEnabled";
            if (multidexExist)
            {
                var firstLineInDefaultConfigScope = line + 1;
                multidexExist = false;
                while (line < gradle.Count && !gradle[line].Contains("buildTypes"))
                {
                    if (gradle[line].Contains(multidexConfig))
                    {
                        if (!required)
                        {
                            gradle.RemoveAt(line);
                            isChanged = true;
                        }
                        multidexExist = true;
                        break;
                    }
                    line++;
                }

                if (!multidexExist && required)
                {
                    gradle.Insert(firstLineInDefaultConfigScope,
                                  "        " + multidexConfig + " true // Enabled by CAS settings");
                    Debug.Log(Utils.logTag + "Enable Multidex in Default Config of " + filePath + Utils.logAutoFeature);
                    isChanged = true;
                }
            }
            return(isChanged);
        }