using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Xml; using RocketGames; using RocketGM; using RocketGM.Models; using RocketLog; using RocketUtils; using UnityEditor; using UnityEngine; #if ROC_GM_PURCHASE using RocketGMPurchase; #endif #if ROC_AD using RocketAd.Models; using RocketUtils.Models; using RocketAd; #endif #if ROC_GM_APPSFLYER using AppsFlyerSDK; #endif namespace RocketGMAppsFlyer { #if ROC_GM_APPSFLYER public class RocGMAppsFlyerController : SingletonBehaviour, IAppsFlyerConversionData #else public class RocGMAppsFlyerController : SingletonBehaviour #endif { private List _excludedFieldList = new List { "click_time", "is_branded_link", "ts", "agency", "http_referrer", "af_cpi", "cost_cents_USD", "is_universal_link", "install_time", "iscache", "af_click_lookback", "af_r", "clickid", "is_first_launch", "redirect_response_data", "orig_cost", "match_type", "retargeting_conversion_type", "af_message" }; [Serializable] public class AppsFlyerUniversalLinkingConfig { #pragma warning disable 649 [Header("General")] public string SubDomain; [Header("Android")] public bool isTargetingAndroid; public string PathPrefix; public string Domain { get { return SubDomain + ".onelink.me"; } } #pragma warning restore 649 } // Appsflyer developer token. Make this serializable field if required. private const string DevToken = "MboAuXcrcdFWry7qYYJK47"; private int _attTimeoutInterval; [SerializeField] private AppsFlyerUniversalLinkingConfig _universalLinkingConfig; [SerializeField] private bool _simulateDeeplink; private AppsFlyerUniversalLinkingConfig _lastSavedConfig; //Triggered when deeplink check is completed. //Sends a bool parameter defining if application opened with deeplink public event Action DeeplinkCheckCompleted; public bool IsDeeplinkCheckCompleted { get { return _isDeeplinkCheckCompleted; } set { _isDeeplinkCheckCompleted = value; if (_isDeeplinkCheckCompleted && DeeplinkCheckCompleted != null) DeeplinkCheckCompleted(!string.IsNullOrEmpty(_afStatus)); } } private bool _isDeeplinkCheckCompleted; //Use this after DeeplinkCheckCompleted public bool IsDeeplinkSession { get { return _afStatus != "Organic"; } } private string _afStatus; private static bool _isAuthorizationInitiatedAtStart; private static int _authorizationCount; private static bool _isInitialAuthorization; protected static readonly RocLog Log = new RocLog("RocGMAppsFlyerController", DebugLevels.Debug); #if ROC_AD #if ROC_AD_MAX_ENABLED private const MediationNetwork MediationNetworkType = MediationNetwork.ApplovinMax; #elif ROC_AD_IRONSOURCE_ENABLED private const MediationNetwork MediationNetworkType = MediationNetwork.IronSource; #else private const MediationNetwork MediationNetworkType = MediationNetwork.IronSource; #endif #endif protected override void OnAwake() { } private void Start() { DontDestroyOnLoad(gameObject); #if UNITY_IOS && ROC_GM_REPORTING RocGmIDFAController.IDFANativeUiAuthorizationInitiated += OnIDFANativeUiAuthorizationInitiated; #endif if (RocGmIDFAController.IsAuthorizationNeeded() && RocGmIDFAController.IDFAAuthorizationStatus == IDFAAuthorizationStatus.NotDetermined) { RocGmIDFAController.AuthorizationStatusChanged += OnAuthorizationStatusChanged; } #if ROC_GM_PURCHASE RocGMPurchaseController.PurchaseItemStarted += OnPurchaseItemStarted; #endif #if ROC_AD RocAd.ImpressionTracked += OnImpressionTracked; RocAdController.InterstitialStartAttempted += OnInterstitialAttempted; RocAdController.RewardedStartAttempted += OnRewardedStartAttempted; RocAdController.AdStarted += OnAdStarted; RocAdController.RewardedVideoFinished += OnRewardedVideoFinished; RocAdController.AdRequestResult += OnAdRequestResult; RocAdController.AdFinished += OnAdFinished; #endif #if ROC_GM_APPSFLYER AppsFlyer.OnDeepLinkReceived += AppsFlyerOnOnDeepLinkReceived; #if ROC_DEBUG_MODE AppsFlyer.setIsDebug(true); #else AppsFlyer.setIsDebug(false); #endif string savedDeviceId = DeviceUtility.DeviceUniqueId; Log.Debug("Device ID: " + savedDeviceId, "Appsflyer Start"); AppsFlyer.setCustomerUserId(savedDeviceId); #if UNITY_IOS if(!string.IsNullOrEmpty(RocGm.GameSettings.AppStoreId)) { AppsFlyer.initSDK(DevToken, RocGm.GameSettings.AppStoreId, this); } else { Log.Warning("Appsflyer cannot be initialized since AppstoreId cannot be found in the project."); } #elif UNITY_ANDROID if (!string.IsNullOrEmpty(Application.identifier)) { AppsFlyer.initSDK(DevToken, Application.identifier, this); AppsFlyer.setCollectAndroidID(true); } else { Log.Warning("Appsflyer cannot be initialized since AndroidId cannot be found in the project."); } #endif #if UNITY_IOS && !UNITY_EDITOR Log.Info("Waiting for ATT User Authorization..."); AppsFlyer.waitForATTUserAuthorizationWithTimeoutInterval(60); #endif Log.Info("ATT authorization result received. Initializing AppsFlyer Sdk..."); #if ROC_GM_PURCHASE AppsFlyerPurchaseConnector.init(this, Store.GOOGLE); AppsFlyerPurchaseConnector.setAutoLogPurchaseRevenue(AppsFlyerAutoLogPurchaseRevenueOptions.AppsFlyerAutoLogPurchaseRevenueOptionsAutoRenewableSubscriptions, AppsFlyerAutoLogPurchaseRevenueOptions.AppsFlyerAutoLogPurchaseRevenueOptionsInAppPurchases); AppsFlyerPurchaseConnector.setPurchaseRevenueValidationListeners(true); AppsFlyerPurchaseConnector.build(); AppsFlyerPurchaseConnector.startObservingTransactions(); #endif //ROC_GM_PURCHASE AppsFlyer.startSDK(); RocGmControllerBase.DoAfterInitialized(() => { if (RocGm.Config.IsFirstSession) { Log.Debug("Sending first session report..."); Report("FirstSession", new Dictionary { { "Source", RocGm.Config.GetNonEmptyRefSource() } }); Log.Debug("First session report sent!"); } }); #endif } private void AppsFlyerOnOnDeepLinkReceived(object sender, EventArgs e) { #if ROC_GM_APPSFLYER AppsFlyer.OnDeepLinkReceived -= AppsFlyerOnOnDeepLinkReceived; Log.Debug("Deeplink is received!"); #endif // ROC_GM_APPSFLYER } public void didReceivePurchaseRevenueValidationInfo(string validationInfo) { #if ROC_GM_APPSFLYER AppsFlyer.AFLog("didReceivePurchaseRevenueValidationInfo", validationInfo); #endif // ROC_GM_APPSFLYER } private void OnIDFANativeUiAuthorizationInitiated(bool atStart, bool isInitial, int count) { _isAuthorizationInitiatedAtStart = atStart; _authorizationCount = count; _isInitialAuthorization = isInitial; ReportAuthorizationEvent("Initiated"); } private void ReportAuthorizationEvent(string decision) { RocGm.DoAfterInitialized(() => { Log.Debug(string.Format("ATTNativeOverlay event Reported!" + "\nExperimentId: {0}" + "\nExperimentVariant: {1}" + "\nResult: {2}" + "\nAtStart: {3}" + "\nIsInitial: {4}" + "\nCount: {5}", RocGm.Config.ExperimentId, RocGm.Config.ExperimentVariant, decision, _isAuthorizationInitiatedAtStart.ToString(CultureInfo.InvariantCulture), _isInitialAuthorization.ToString(CultureInfo.InvariantCulture), _authorizationCount)); Report("ATTNativeOverlay", new Dictionary { {"ExperimentId", RocGm.Config.ExperimentId.ToString(CultureInfo.InvariantCulture)}, {"ExperimentVariant", RocGm.Config.ExperimentVariant.ToString(CultureInfo.InvariantCulture)}, {"Result", decision}, {"AtStart", _isAuthorizationInitiatedAtStart.ToString(CultureInfo.InvariantCulture)}, {"IsInitial", _isInitialAuthorization.ToString(CultureInfo.InvariantCulture)}, {"Count", _authorizationCount.ToString(CultureInfo.InvariantCulture)} }); }); } private void OnAuthorizationStatusChanged(IDFAAuthorizationStatus status) { Log.Debug("Report Authorization status: ", status.ToString()); ReportAuthorizationEvent(status.ToString()); } #if ROC_GM_APPSFLYER //Mark AppsFlyer CallBacks public void onConversionDataSuccess(string conversionData) { Log.Debug("didReceiveConversionData", conversionData); Log.Debug("AppsFlyerId: " + AppsFlyer.getAppsFlyerId()); var conversionDataDictionary = AppsFlyer.CallbackStringToDictionary(conversionData); var isFirstLaunch = conversionDataDictionary.ContainsKey("is_first_launch") && (bool)conversionDataDictionary["is_first_launch"]; foreach (var excludedField in _excludedFieldList) { if (conversionDataDictionary.ContainsKey(excludedField)) { conversionDataDictionary.Remove(excludedField); } } //Add appsFlyerId if(!conversionDataDictionary.ContainsKey("appsFlyerId")) conversionDataDictionary.Add("appsFlyerId", AppsFlyer.getAppsFlyerId()); if (isFirstLaunch) { Log.Debug("First Launch, writing conversion data"); RocGm.Config.ConversionData = DictToStr(conversionDataDictionary); } var afStatus = ""; if (conversionDataDictionary.ContainsKey("af_status")) afStatus = (string) conversionDataDictionary["af_status"]; SetAttribution(afStatus); } public void onConversionDataFail(string error) { Log.Debug("didReceiveConversionDataWithError", error); } public void onAppOpenAttribution(string attributionData) { Log.Debug("onAppOpenAttribution", attributionData); } public void onAppOpenAttributionFailure(string error) { Log.Debug("onAppOpenAttributionFailure", error); } private string DictToStr(Dictionary dict) { return AFMiniJSON.Json.Serialize(dict); } #endif public void Report(string eventName, Dictionary eventValues = null) { #if ROC_GM_APPSFLYER Log.Debug($"Triggering AppsFlyer report function for: ${eventName}"); AppsFlyer.sendEvent(eventName, eventValues); #endif } public void SendLevelReachedEvent(int levelReached) { Log.Debug("Sending level reached event: " + levelReached); Report($"level_reached_{levelReached}"); } private void SetAttribution(string afStatus) { _afStatus = afStatus; Log.Info(IsDeeplinkSession ? "Valid Deeplink is FOUND!" : "Valid deeplink could NOT be found!"); if (!RocGm.Config.IsAttributionUpdated) { RocGm.DoAfterInitialized(() => { RocGm.Reporting.UpdatePlayerAttribution(); }); } else { Log.Debug("Attribution is ALREADY updated!"); } IsDeeplinkCheckCompleted = true; } #if ROC_AD private void OnImpressionTracked(RocImpressionData rocImpressionData) { #if ROC_GM_APPSFLYER // Create a map that will store our impression data Dictionary appsFlyerImpressionMap = new Dictionary(); // Fill required fields to our map appsFlyerImpressionMap.Add(AdRevenueScheme.AD_TYPE, rocImpressionData.AdFormat); appsFlyerImpressionMap.Add(AdRevenueScheme.AD_UNIT, rocImpressionData.AdUnit); appsFlyerImpressionMap.Add(AdRevenueScheme.COUNTRY, rocImpressionData.Country); appsFlyerImpressionMap.Add(AdRevenueScheme.PLACEMENT, rocImpressionData.Placement); appsFlyerImpressionMap.Add("ad_network", rocImpressionData.AdNetwork); appsFlyerImpressionMap.Add("revenue", rocImpressionData.Revenue.ToString(CultureInfo.InvariantCulture)); appsFlyerImpressionMap.Add("currency", rocImpressionData.Currency); Log.Debug($"AppsFlyerAdRevenue tracked impression data: {rocImpressionData.AdFormat}, {rocImpressionData.AdUnit}, " + $"{rocImpressionData.AdNetwork}, {rocImpressionData.Revenue}, {rocImpressionData.Currency}, {rocImpressionData.Country}, {rocImpressionData.Placement}"); var data = new AFAdRevenueData(rocImpressionData.AdNetwork, MediationNetworkType, rocImpressionData.Currency, rocImpressionData.Revenue); AppsFlyer.logAdRevenue(data, appsFlyerImpressionMap); #endif } private void OnAdStarted(AdFormat adFormat) { if (adFormat == AdFormat.RewardedVideo) { Log.Debug("Reported RewardedShow"); Report("RewardedShow"); } else if (adFormat == AdFormat.Interstitial) { Log.Debug("Reported InterstitialShow"); Report("InterstitialShow"); } } private void OnInterstitialAttempted() { Log.Debug("Reported InterstitialAttempt"); Report("InterstitialAttempt"); } private void OnRewardedStartAttempted() { Log.Debug("Reported rewarded video attempt"); Report("RewardedAttempt"); } private void OnAdRequestResult(AdFormat adFormat, AdNetworkResult adNetworkResult) { if (adFormat != AdFormat.Banner || adNetworkResult != AdNetworkResult.Fill) { return; } Report("BannerWatched"); } private void OnAdFinished(AdFormat adFormat, AdUserResult adUserResult) { if (adFormat == AdFormat.RewardedVideo) { if (adUserResult == AdUserResult.NotAvailable) { Report("RewardedNotReady"); } } } private void OnRewardedVideoFinished(string videoSource, AdUserResult adUserResult, Dictionary parameters) { if (string.IsNullOrEmpty(videoSource)) { Log.Warning("Video source is EMPTY!"); return; } Log.Debug("Video Source: " + videoSource); Report("RewardedVideoDetails", new Dictionary() { {"Reason", videoSource}, {"Result", adUserResult.ToString()} }); } #endif #if ROC_GM_PURCHASE && ROC_GM_APPSFLYER private void OnPurchaseItemStarted(RocGMPurchaseController.GMPurchaseProduct gmPurchaseProduct) { Report(AFInAppEvents.INITIATED_CHECKOUT); } #endif #if UNITY_EDITOR private void OnValidate() { string configPath = Application.dataPath + "/RocketGames/RocGMAppsFlyerSdkConfig.txt"; if (_lastSavedConfig == null && File.Exists(configPath)) { using (StreamReader r = new StreamReader(configPath)) { string json = r.ReadToEnd(); _lastSavedConfig = JsonUtility.FromJson(json); } } if (_universalLinkingConfig == null) return; if ((_universalLinkingConfig.isTargetingAndroid && string.IsNullOrEmpty(_universalLinkingConfig.PathPrefix) || string.IsNullOrEmpty(_universalLinkingConfig.SubDomain))) return; if (_lastSavedConfig == null || _lastSavedConfig.PathPrefix != _universalLinkingConfig.PathPrefix || _lastSavedConfig.SubDomain != _universalLinkingConfig.SubDomain) { var jsonString = JsonUtility.ToJson(_universalLinkingConfig); File.WriteAllText(configPath, jsonString); _lastSavedConfig = new AppsFlyerUniversalLinkingConfig { PathPrefix = _universalLinkingConfig.PathPrefix, SubDomain = _universalLinkingConfig.SubDomain }; ModifyAndroidManifest(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } } private void ModifyAndroidManifest() { string path = Application.dataPath + "/RocketGames/RocketGMAppsFlyer/Plugins/Android/RocketGMAppsFlyer.plugin/AndroidManifest.xml"; if (!File.Exists(path)) return; var manifestDoc = new XmlDocument(); manifestDoc.LoadXml(File.ReadAllText(path)); var nodes = manifestDoc.SelectNodes("manifest/application/activity/intent-filter/data"); if (nodes != null) { foreach (XmlElement node in nodes) { node.SetAttribute("android:host", _universalLinkingConfig.Domain); node.SetAttribute("android:pathPrefix", "/" + _universalLinkingConfig.PathPrefix); } manifestDoc.Save(path); } } #endif } }