diff --git a/Classes/AppDelegate.cpp b/Classes/AppDelegate.cpp index 3fda54b..e430863 100644 --- a/Classes/AppDelegate.cpp +++ b/Classes/AppDelegate.cpp @@ -140,13 +140,18 @@ static void didResolveDeepLink(AppsFlyerXDeepLinkResult result){ bool AppDelegate::applicationDidFinishLaunching() { AppsFlyerX::stop(false); - +// AppsFlyerX::enableTCFDataCollection(true); AppsFlyerX::setIsDebug(true); +// AppsFlyerX::setConsentData(AppsFlyerXConsent::initNonGDPRUser()); + // AppsFlyerX::setConsentData(AppsFlyerXConsent::initForGDPRUser(true,true)); //AppsFlyerX::setMinTimeBetweenSessions(9); AppsFlyerX::setAppsFlyerDevKey("devkey"); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) - AppsFlyerX::setAppleAppID("appid"); + // // In case you want to use manual mode. + // AppsFlyerX::setManualStart(true); + // // + AppsFlyerX::setAppleAppID("appleAppId"); // AppsFlyerX::waitForATTUserAuthorizationWithTimeoutInterval(60); #endif @@ -166,10 +171,12 @@ bool AppDelegate::applicationDidFinishLaunching() { data["isLegacy"] = false; AppsFlyerX::setPartnerData("partnerID", data); - AppsFlyerX::logEvent(AFEventPurchase, {{ "key1", cocos2d::Value("value1")}, - { "key2", cocos2d::Value("value2")}}); +// AppsFlyerX::logEvent(AFEventPurchase, {{ "key1", cocos2d::Value("value1")}, +// { "key2", cocos2d::Value("value2")}}); #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) - AppsFlyerX::start(); +// remove this to use manual mode. + AppsFlyerX::start(); +// #endif // initialize director @@ -241,7 +248,9 @@ void AppDelegate::applicationWillEnterForeground() { //CCLOG("%s", "~+~+~+~+~ applicationWillEnterForeground ~+~+~+~+~"); #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) - AppsFlyerX::start(); +// remove this to use manual mode. + AppsFlyerX::start(); +// #endif #if USE_AUDIO_ENGINE diff --git a/Classes/AppsFlyer/AppsFlyerX.cpp b/Classes/AppsFlyer/AppsFlyerX.cpp index c22d247..c1b4224 100644 --- a/Classes/AppsFlyer/AppsFlyerX.cpp +++ b/Classes/AppsFlyer/AppsFlyerX.cpp @@ -16,6 +16,28 @@ #include "AppsFlyerXApple.h" #endif +bool AppsFlyerX::manualStart = false; + +void AppsFlyerX::setManualStart(bool isManualStart) { + manualStart = isManualStart; +} + +//static void enableTCFDataCollection(bool shouldCollectConsentData); +void AppsFlyerX::enableTCFDataCollection(bool shouldCollectConsentData) { +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + AppsFlyerXAndroid::enableTCFDataCollection(shouldCollectConsentData); +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerXApple::enableTCFDataCollection(shouldCollectConsentData); +#endif +} + +void AppsFlyerX::setConsentData(const AppsFlyerXConsent& consentData){ +#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + AppsFlyerXAndroid::setConsentData(consentData); +#elif (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerXApple::setConsentData(consentData); +#endif +} void AppsFlyerX::setCustomerUserID(const std::string& customerUserID) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) diff --git a/Classes/AppsFlyer/AppsFlyerX.h b/Classes/AppsFlyer/AppsFlyerX.h index b550c2c..0998fae 100644 --- a/Classes/AppsFlyer/AppsFlyerX.h +++ b/Classes/AppsFlyer/AppsFlyerX.h @@ -15,11 +15,18 @@ #include "EmailCryptTypeX.h" #include "AppsFlyerXMacro.h" #include "AppsFlyerXDeepLinkResult.h" +#include "AppsFlyerXConsent.cpp" class AppsFlyerX { public: - + static bool manualStart; + static void setManualStart(bool isManualStart); + + static void enableTCFDataCollection(bool shouldCollectConsentData); + + static void setConsentData(const AppsFlyerXConsent& consentData); + static void setCustomerUserID(const std::string& customerUserID); static std::string customerUserID(); diff --git a/Classes/AppsFlyer/AppsFlyerXAndroid.cpp b/Classes/AppsFlyer/AppsFlyerXAndroid.cpp index afb03f2..5d05f54 100644 --- a/Classes/AppsFlyer/AppsFlyerXAndroid.cpp +++ b/Classes/AppsFlyer/AppsFlyerXAndroid.cpp @@ -12,7 +12,7 @@ std::string afDevKey; bool isConveriosnListenerInitialized = false; bool isSubscribedForDeepLink = false; -const char *pluginVersion = "6.10.3"; +const char *pluginVersion = "6.13.0"; // Headers void initConvertionCallback(); @@ -50,6 +50,72 @@ cocos2d::JniMethodInfo getAppsFlyerInstance() { return jniGetInstance; } +/* + * AppsFlyerLib.getInstance().enableTCFDataCollection(true); + */ + +void AppsFlyerXAndroid::enableTCFDataCollection(bool shouldCollectConsentData) { + callVoidMethodWithBoolParam(shouldCollectConsentData, "enableTCFDataCollection", "(Z)V"); +} + +void AppsFlyerXAndroid::setConsentData(const AppsFlyerXConsent& consentData){ + cocos2d::JniMethodInfo jniGetConsentInstance; + jobject resultConsent; + + if (consentData.IsUserSubjectToGDPR()) { + bool hasConsentForDataUsage = consentData.HasConsentForDataUsage(); + bool hasConsentForAdsPersonalization = consentData.HasConsentForAdsPersonalization(); + jboolean jHasConsentForDataUsage = (jboolean)hasConsentForDataUsage; + jboolean jHasConsentForAdsPersonalization = (jboolean)hasConsentForAdsPersonalization; + if (cocos2d::JniHelper::getStaticMethodInfo(jniGetConsentInstance, + "com/appsflyer/AppsFlyerConsent", + "forGDPRUser", + "(ZZ)Lcom/appsflyer/AppsFlyerConsent;")) { + + resultConsent = (jobject) jniGetConsentInstance. + env->CallStaticObjectMethod(jniGetConsentInstance.classID, jniGetConsentInstance.methodID, + jHasConsentForDataUsage, + jHasConsentForAdsPersonalization); + } + else{ + CCLOG("%s", "'AppsFlyerConsent' is not loaded"); + return; + } + } + else{ + if (cocos2d::JniHelper::getStaticMethodInfo(jniGetConsentInstance, + "com/appsflyer/AppsFlyerConsent", + "forNonGDPRUser", + "()Lcom/appsflyer/AppsFlyerConsent;")) { + resultConsent = (jobject) jniGetConsentInstance. + env->CallStaticObjectMethod(jniGetConsentInstance.classID, jniGetConsentInstance.methodID); + } + else{ + CCLOG("%s", "'AppsFlyerConsent' is not loaded"); + return; + } + } + cocos2d::JniMethodInfo jniGetInstance = getAppsFlyerInstance(); + + jobject afInstance = (jobject) jniGetInstance.env->CallStaticObjectMethod( + jniGetInstance.classID, jniGetInstance.methodID); + + if (NULL != afInstance) { + + jclass cls = jniGetInstance.env->GetObjectClass(afInstance); + + jmethodID methodId = jniGetInstance.env->GetMethodID(cls, "setConsentData", "(Lcom/appsflyer/AppsFlyerConsent;)V"); + + jniGetInstance.env->CallVoidMethod(afInstance, methodId, resultConsent); + + jniGetInstance.env->DeleteLocalRef(resultConsent); + jniGetInstance.env->DeleteLocalRef(afInstance); + jniGetInstance.env->DeleteLocalRef(jniGetInstance.classID); + } else { + CCLOGERROR("%s", "'AppsFlyerLib' is not loaded"); + } + +} void AppsFlyerXAndroid::stop(bool shouldStop) { @@ -277,26 +343,8 @@ void AppsFlyerXAndroid::start() { jniGetInstance.classID, jniGetInstance.methodID); if (NULL != afInstance) { - //CCLOG("%s", "com/appsflyer/AppsFlyerLib is loaded"); - jclass cls = jniGetInstance.env->GetObjectClass(afInstance); - jclass clsExtension = jniGetInstance.env->FindClass("com/appsflyer/internal/platform_extension/PluginInfo"); - - - - jmethodID extensionConstructor = jniGetInstance.env->GetMethodID(clsExtension, - "","(Lcom/appsflyer/internal/platform_extension/Plugin;Ljava/lang/String;)V"); - - jclass enumClass = jniGetInstance.env->FindClass("com/appsflyer/internal/platform_extension/Plugin"); - jfieldID fid = jniGetInstance.env->GetStaticFieldID(enumClass, "COCOS_2DX","Lcom/appsflyer/internal/platform_extension/Plugin;"); - jobject plugin = jniGetInstance.env->GetStaticObjectField(enumClass, fid); - jstring version = jniGetInstance.env->NewStringUTF(pluginVersion); - jobject extensionObject = jniGetInstance.env->NewObject(clsExtension, extensionConstructor, plugin, version); - - callSetPluginInfo(extensionObject); - - cocos2d::JniMethodInfo jniGetContext; if (!cocos2d::JniHelper::getStaticMethodInfo(jniGetContext, @@ -312,17 +360,6 @@ void AppsFlyerXAndroid::start() { jstring jAppsFlyerDevKey = jniGetInstance.env->NewStringUTF(afDevKey.c_str()); - // call Init if no GCD registered - if (!isConveriosnListenerInitialized) { - jmethodID initMethodId = jniGetInstance.env->GetMethodID(cls, - "init", - "(Ljava/lang/String;Lcom/appsflyer/AppsFlyerConversionListener;Landroid/content/Context;)Lcom/appsflyer/AppsFlyerLib;"); - - // This is what we actually do: afLib.init(appsFlyerDevKey, null) - jniGetInstance.env->CallObjectMethod(afInstance, initMethodId, jAppsFlyerDevKey, NULL, jContext); - } - - //public void trackAppLaunch(Context ctx, String devKey) jmethodID startTrackingMethodId = jniGetInstance.env->GetMethodID(cls, "start", "(Landroid/content/Context;Ljava/lang/String;)V"); @@ -412,6 +449,75 @@ void AppsFlyerXAndroid::setUserEmails(std::vector userEmails, Email void AppsFlyerXAndroid::setAppsFlyerDevKey(const std::string &appsFlyerDevKey) { afDevKey = appsFlyerDevKey; + + if (afDevKey.empty()) { + CCLOGWARN("%s", "AppsFlyer Dev Key is not provided"); + return; + } + + cocos2d::JniMethodInfo jniGetInstance = getAppsFlyerInstance(); + + //AppsFlyerLib afLib instance + jobject afInstance = (jobject) jniGetInstance.env->CallStaticObjectMethod( + jniGetInstance.classID, jniGetInstance.methodID); + + if (NULL != afInstance) { + //CCLOG("%s", "com/appsflyer/AppsFlyerLib is loaded"); + + jclass cls = jniGetInstance.env->GetObjectClass(afInstance); + + jclass clsExtension = jniGetInstance.env->FindClass( + "com/appsflyer/internal/platform_extension/PluginInfo"); + + + jmethodID extensionConstructor = jniGetInstance.env->GetMethodID(clsExtension, + "", + "(Lcom/appsflyer/internal/platform_extension/Plugin;Ljava/lang/String;)V"); + + jclass enumClass = jniGetInstance.env->FindClass( + "com/appsflyer/internal/platform_extension/Plugin"); + jfieldID fid = jniGetInstance.env->GetStaticFieldID(enumClass, "COCOS_2DX", + "Lcom/appsflyer/internal/platform_extension/Plugin;"); + jobject plugin = jniGetInstance.env->GetStaticObjectField(enumClass, fid); + jstring version = jniGetInstance.env->NewStringUTF(pluginVersion); + jobject extensionObject = jniGetInstance.env->NewObject(clsExtension, extensionConstructor, + plugin, version); + + callSetPluginInfo(extensionObject); + + + cocos2d::JniMethodInfo jniGetContext; + + if (!cocos2d::JniHelper::getStaticMethodInfo(jniGetContext, + "org/cocos2dx/lib/Cocos2dxActivity", + "getContext", + "()Landroid/content/Context;")) { + return; + } + + jobject jContext = (jobject) jniGetContext.env->CallStaticObjectMethod( + jniGetContext.classID, jniGetContext.methodID); + + + jstring jAppsFlyerDevKey = jniGetInstance.env->NewStringUTF(afDevKey.c_str()); + + // call Init if no GCD registered + if (!isConveriosnListenerInitialized) { + jmethodID initMethodId = jniGetInstance.env->GetMethodID(cls, + "init", + "(Ljava/lang/String;Lcom/appsflyer/AppsFlyerConversionListener;Landroid/content/Context;)Lcom/appsflyer/AppsFlyerLib;"); + + // This is what we actually do: afLib.init(appsFlyerDevKey, null) + jniGetInstance.env->CallObjectMethod(afInstance, initMethodId, jAppsFlyerDevKey, NULL, + jContext); + + jniGetInstance.env->DeleteLocalRef(afInstance); + jniGetInstance.env->DeleteLocalRef(jniGetInstance.classID); + } + } + else { + CCLOGERROR("%s", "'AppsFlyerLib' is not loaded"); + } } std::string AppsFlyerXAndroid::appsFlyerDevKey() { diff --git a/Classes/AppsFlyer/AppsFlyerXAndroid.h b/Classes/AppsFlyer/AppsFlyerXAndroid.h index f212c24..fc273c5 100644 --- a/Classes/AppsFlyer/AppsFlyerXAndroid.h +++ b/Classes/AppsFlyer/AppsFlyerXAndroid.h @@ -10,6 +10,8 @@ #include "EmailCryptTypeX.h" #include "AppsFlyerXDeepLinkResult.h" +#include "AppsFlyerX.h" + class AppsFlyerXAndroid { private: @@ -24,6 +26,10 @@ class AppsFlyerXAndroid { static AppsFlyerXAndroid* getInstance(); + static void enableTCFDataCollection(bool shouldCollectConsentData); + + static void setConsentData(const AppsFlyerXConsent& consentData); + static void didEnterBackground(); static void setCustomerUserID(const std::string& customerUserID); diff --git a/Classes/AppsFlyer/AppsFlyerXApple.h b/Classes/AppsFlyer/AppsFlyerXApple.h index c8e5fdf..7bead4d 100644 --- a/Classes/AppsFlyer/AppsFlyerXApple.h +++ b/Classes/AppsFlyer/AppsFlyerXApple.h @@ -34,6 +34,10 @@ class AppsFlyerXApple { static AppsFlyerXApple* getInstance(); + static void enableTCFDataCollection(bool shouldCollectConsentData); + + static void setConsentData(const AppsFlyerXConsent& consentData); + static void setCustomerUserID(const std::string& customerUserID); static std::string customerUserID(); diff --git a/Classes/AppsFlyer/AppsFlyerXApple.mm b/Classes/AppsFlyer/AppsFlyerXApple.mm index 9b1f352..0e31954 100644 --- a/Classes/AppsFlyer/AppsFlyerXApple.mm +++ b/Classes/AppsFlyer/AppsFlyerXApple.mm @@ -7,12 +7,14 @@ // - +#include "AppsFlyerX.h" #include "AppsFlyerXApple.h" #include "AppsFlyerXAppleHelper.h" #include "AppsFlyerXAppleDelegate.h" #include "AppsFlyerXAppleDeepLinkDelegate.h" #import "libAppsFlyer/AppsFlyerLib.h" +#import + /* Null, because instance will be initialized on demand. */ AppsFlyerXApple* AppsFlyerXApple::instance = 0; @@ -37,6 +39,19 @@ AppsFlyerXApple::AppsFlyerXApple() {} +void AppsFlyerXApple::enableTCFDataCollection(bool shouldCollectConsentData){ + [[AppsFlyerLib shared] enableTCFDataCollection:shouldCollectConsentData]; +} + +void AppsFlyerXApple::setConsentData(const AppsFlyerXConsent& consentData){ + if (consentData.IsUserSubjectToGDPR()){ + [[AppsFlyerLib shared] setConsentData:[[AppsFlyerConsent alloc] initForGDPRUserWithHasConsentForDataUsage:consentData.HasConsentForDataUsage() hasConsentForAdsPersonalization:consentData.HasConsentForAdsPersonalization()]]; + } + else{ + [[AppsFlyerLib shared] setConsentData:[[AppsFlyerConsent alloc] initNonGDPRUser]]; + } +} + void AppsFlyerXApple::setCustomerUserID(const std::string& customerUserID) { [[AppsFlyerLib shared] setCustomerUserID: [NSString stringWithUTF8String:customerUserID.c_str()]]; } @@ -59,7 +74,7 @@ static AppsFlyerXApple *xApple = nil; static AppsFlyerXAppleDelegate *delegate = nil; [[AppsFlyerLib shared] setPluginInfoWith: AFSDKPluginCocos2dx - pluginVersion:@"6.10.3" + pluginVersion:@"6.13.0" additionalParams:nil]; dispatch_once(&onceToken, ^{ @@ -71,7 +86,9 @@ object: nil queue: nil usingBlock: ^ (NSNotification * note) { - [[AppsFlyerLib shared] start]; + if (AppsFlyerX::manualStart == false) { + [[AppsFlyerLib shared] start]; + } }]; }); diff --git a/Classes/AppsFlyer/AppsFlyerXConsent.cpp b/Classes/AppsFlyer/AppsFlyerXConsent.cpp new file mode 100644 index 0000000..b51fced --- /dev/null +++ b/Classes/AppsFlyer/AppsFlyerXConsent.cpp @@ -0,0 +1,39 @@ +// +// AppsFlyerXConsent.h +// install_test_2 +// +// Created by Moris Gateno on 04/03/2024. +// + +#ifndef AppsFlyerXConsent_h +#define AppsFlyerXConsent_h + +class AppsFlyerXConsent{ +private: + bool isUserSubjectToGDPR; + bool hasConsentForDataUsage; + bool hasConsentForAdsPersonalization; + + AppsFlyerXConsent(bool isConsentForDataUsage, bool isConsentForAdsPersonalization): isUserSubjectToGDPR(true), hasConsentForDataUsage(isConsentForDataUsage), hasConsentForAdsPersonalization(isConsentForAdsPersonalization){} + + AppsFlyerXConsent(): isUserSubjectToGDPR(false), + hasConsentForDataUsage(false), hasConsentForAdsPersonalization(false) {} + +public: +// methods to get the private fields + bool IsUserSubjectToGDPR() const { return isUserSubjectToGDPR; } + bool HasConsentForDataUsage() const { return hasConsentForDataUsage; } + bool HasConsentForAdsPersonalization() const { return hasConsentForAdsPersonalization; } + + static AppsFlyerXConsent initNonGDPRUser() { + AppsFlyerXConsent c; + return c; + } + + static AppsFlyerXConsent initForGDPRUser(bool hasConsentForDataUsage, bool hasConsentForAdsPersonalization) { + AppsFlyerXConsent b(hasConsentForDataUsage, hasConsentForAdsPersonalization); + return b; + } +}; + +#endif /* AppsFlyerXConsent_h */ diff --git a/Classes/AppsFlyer/libAppsFlyer/AppsFlyerConsent.h b/Classes/AppsFlyer/libAppsFlyer/AppsFlyerConsent.h new file mode 100644 index 0000000..564912a --- /dev/null +++ b/Classes/AppsFlyer/libAppsFlyer/AppsFlyerConsent.h @@ -0,0 +1,26 @@ +// +// AppsFlyerConsent.h +// AppsFlyerLib +// +// Created by Veronica Belyakov on 14/01/2024. +// +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface AppsFlyerConsent : NSObject + +@property (nonatomic, readonly, assign) BOOL isUserSubjectToGDPR; +@property (nonatomic, readonly, assign) BOOL hasConsentForDataUsage; +@property (nonatomic, readonly, assign) BOOL hasConsentForAdsPersonalization; + +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)new NS_UNAVAILABLE; + +- (instancetype)initForGDPRUserWithHasConsentForDataUsage:(BOOL)hasConsentForDataUsage + hasConsentForAdsPersonalization:(BOOL)hasConsentForAdsPersonalization NS_DESIGNATED_INITIALIZER; +- (instancetype)initNonGDPRUser NS_DESIGNATED_INITIALIZER; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLib.h b/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLib.h index e92f32e..3ed2d29 100644 --- a/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLib.h +++ b/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLib.h @@ -2,7 +2,7 @@ // AppsFlyerLib.h // AppsFlyerLib // -// AppsFlyer iOS SDK 6.10.1 (109) +// AppsFlyer iOS SDK 6.13.1 (150) // Copyright (c) 2012-2023 AppsFlyer Ltd. All rights reserved. // @@ -12,6 +12,7 @@ #import "AppsFlyerShareInviteHelper.h" #import "AppsFlyerDeepLinkResult.h" #import "AppsFlyerDeepLink.h" +#import "AppsFlyerConsent.h" NS_ASSUME_NONNULL_BEGIN @@ -140,7 +141,7 @@ typedef enum { EmailCryptTypeSHA256 = 3 } EmailCryptType; -typedef NS_CLOSED_ENUM (NSInteger ,AFSDKPlugin) { +typedef NS_CLOSED_ENUM(NSInteger, AFSDKPlugin) { AFSDKPluginIOSNative, AFSDKPluginUnity, AFSDKPluginFlutter, @@ -159,7 +160,6 @@ typedef NS_CLOSED_ENUM (NSInteger ,AFSDKPlugin) { AFSDKPluginAdobeSwiftAEP } NS_SWIFT_NAME(Plugin); -@class AFSDKPluginInfo; NS_SWIFT_NAME(DeepLinkDelegate) @protocol AppsFlyerDeepLinkDelegate @@ -351,7 +351,7 @@ NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); [[AppsFlyerLib shared] setResolveDeepLinkURLs:@[@"domain.com", @"subdomain.domain.com"]]; */ -@property(nonatomic, nullable) NSArray *resolveDeepLinkURLs; +@property(nonatomic, nullable, copy) NSArray *resolveDeepLinkURLs; /** For advertisers who use vanity OneLinks. @@ -362,12 +362,12 @@ NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); [[AppsFlyerLib shared] oneLinkCustomDomains:@[@"domain.com", @"subdomain.domain.com"]]; */ -@property(nonatomic, nullable) NSArray *oneLinkCustomDomains; +@property(nonatomic, nullable, copy) NSArray *oneLinkCustomDomains; /* * Set phone number for each `start` event. `phoneNumber` will be sent as SHA256 string */ -@property(nonatomic, nullable) NSString *phoneNumber; +@property(nonatomic, nullable, copy) NSString *phoneNumber; - (NSString *)phoneNumber UNAVAILABLE_ATTRIBUTE; @@ -390,13 +390,16 @@ NS_SWIFT_NAME(waitForATTUserAuthorization(timeoutInterval:)); AppsFlyerLib.shared().currentDeviceLanguage("EN") */ -@property(nonatomic, nullable) NSString *currentDeviceLanguage; +@property(nonatomic, nullable, copy) NSString *currentDeviceLanguage; /** Internal API. Please don't use. */ -- (void)setPluginInfoWith:(AFSDKPlugin)plugin pluginVersion:(NSString *)version additionalParams:(NSDictionary * _Nullable)additionalParams +- (void)setPluginInfoWith:(AFSDKPlugin)plugin + pluginVersion:(NSString *)version + additionalParams:(NSDictionary * _Nullable)additionalParams NS_SWIFT_NAME(setPluginInfo(plugin:version:additionalParams:)); + /** Enable the collection of Facebook Deferred AppLinks Requires Facebook SDK and Facebook app on target/client device. @@ -578,6 +581,11 @@ NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); */ - (void)remoteDebuggingCallWithData:(NSString *)data; +/** + This is for internal use. + */ +- (void)remoteDebuggingCallV2WithData:(NSString *)dataAsString; + /** Used to force the trigger `onAppOpenAttribution` delegate. Notice, re-engagement, session and launch won't be counted. @@ -634,13 +642,13 @@ NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); /** API to set manually Facebook deferred app link */ -@property(nonatomic, nullable) NSURL *facebookDeferredAppLink; +@property(nonatomic, nullable, copy) NSURL *facebookDeferredAppLink; /** Block an events from being shared with ad networks and other 3rd party integrations Must only include letters/digits or underscore, maximum length: 45 */ -@property(nonatomic, nullable) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); +@property(nonatomic, nullable, copy) NSArray *sharingFilter DEPRECATED_MSG_ATTRIBUTE("starting SDK version 6.4.0, please use `setSharingFilterForPartners:`"); @property(nonatomic) NSUInteger deepLinkTimeout; @@ -661,6 +669,27 @@ NS_SWIFT_NAME(logEvent(name:values:completionHandler:)); */ - (void)setSharingFilterForPartners:(NSArray * _Nullable)sharingFilter; + +/** + Sets or updates the user consent data related to GDPR and DMA regulations for advertising and data usage + purposes within the application. This method must be invoked with the user's current consent status each + time the app starts or whenever there is a change in the user's consent preferences. + + Note that this method does not persist the consent data across app sessions; it only applies for the + duration of the current app session. If you wish to stop providing the consent data, you should + cease calling this method. + + @param consent an instance of AppsFlyerConsent that encapsulates the user's consent information. + */ +- (void)setConsentData:(AppsFlyerConsent *)consent; + +/** + Enable the SDK to collect and send TCF data + + @param shouldCollectConsentData indicates if the TCF data collection is enabled. + */ +- (void)enableTCFDataCollection:(BOOL)shouldCollectConsentData; + /** Validate if URL contains certain string and append quiery parameters to deeplink URL. In case if URL does not contain user-defined string, diff --git a/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLinkGenerator.h b/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLinkGenerator.h index b917074..d3ec8f4 100644 --- a/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLinkGenerator.h +++ b/Classes/AppsFlyer/libAppsFlyer/AppsFlyerLinkGenerator.h @@ -22,7 +22,7 @@ NS_ASSUME_NONNULL_BEGIN /// from `-[AppsFlyerShareInviteHelper generateInviteUrlWithLinkGenerator:completionHandler]` + (instancetype)new NS_UNAVAILABLE; -@property(nonatomic, nullable) NSString *brandDomain; +@property(nonatomic, nullable, copy) NSString *brandDomain; /// The channel through which the invite was sent (e.g. Facebook/Gmail/etc.). Usage: Recommended - (void)setChannel :(nonnull NSString *)channel; diff --git a/Classes/AppsFlyer/libAppsFlyer/libAppsFlyerLib.a b/Classes/AppsFlyer/libAppsFlyer/libAppsFlyerLib.a index 0c9475c..7dbcd1a 100644 Binary files a/Classes/AppsFlyer/libAppsFlyer/libAppsFlyerLib.a and b/Classes/AppsFlyer/libAppsFlyer/libAppsFlyerLib.a differ diff --git a/Classes/HelloWorldScene.cpp b/Classes/HelloWorldScene.cpp index c8a4a7f..36c0fe6 100644 --- a/Classes/HelloWorldScene.cpp +++ b/Classes/HelloWorldScene.cpp @@ -1,5 +1,5 @@ #include "HelloWorldScene.h" -#include "SimpleAudioEngine.h" +//#include "SimpleAudioEngine.h" USING_NS_CC; @@ -53,7 +53,12 @@ bool HelloWorld::init() case Widget::TouchEventType::ENDED: { std::cout << "Log event raised" << std::endl; - + //In case you use manual mode + // #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + // AppsFlyerX::setManualStart(false); + // #endif + // AppsFlyerX::start(); + // // ValueMap map; map["key1"] = "value1"; map["key2"] = 1; diff --git a/README.md b/README.md index 1d33742..0ad4f96 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ - + # Cocos2dX AppsFlyer plugin for Android and iOS. -[![GitHub tag](https://img.shields.io/github/v/release/AppsFlyerSDK/appsflyer-unity-plugin)](https://img.shields.io/github/v/release/AppsFlyerSDK/appsflyer-unity-plugin) ---------- @@ -16,6 +15,7 @@ In order for us to provide optimal support, we would kindly ask you to submit an - [integration](#integration) - [Usage](#usage) +- [Manual start mode](#manual-start) - [API methods](#api-methods) - [setIsDebug](#setIsDebug) - [stop](#stopTracking) @@ -54,12 +54,13 @@ In order for us to provide optimal support, we would kindly ask you to submit an - [setCurrentDeviceLanguage](#currentLang) *(ios only)* - [setSharingFilterForPartners](#SharingFilterForPartners) - [setDisableNetworkData](#disableNetworkID) *(android only)* +- [Send consent for DMA compliance](#dma_support) ### This plugin is built for -- Android AppsFlyer SDK **v6.10.3** -- iOS AppsFlyer SDK **v6.10.1** +- Android AppsFlyer SDK **v6.13.0** +- iOS AppsFlyer SDK **v6.13.1** ### Integration: @@ -116,9 +117,43 @@ void AppDelegate::applicationDidEnterBackground() { ``` +## Manual mode: +Starting version 6.13.0, we support a manual mode to seperate the initialization of the AppsFlyer SDK and the start of the SDK. In this case, the AppsFlyer SDK won't start automatically, giving the developer more freedom when to start the AppsFlyer SDK. Please note that in manual mode, the developer is required to implement the API start() in order to start the SDK. +
If you are using CMP to collect consent data this feature is needed. See explanation [here](#dma_support). +### Examples: + +```cpp +bool AppDelegate::applicationDidFinishLaunching() { +... + AppsFlyerX::setAppsFlyerDevKey("devkey"); + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + // In case you want to use manual mode. + AppsFlyerX::setManualStart(true); + // + AppsFlyerX::setAppleAppID("appleAppId"); +... +} +``` +- See that we aren't calling any AppsFlyerX::start() in any case. +The init function is called in the background in order to catch any deeplinking. -##
API Methods +And to start the AppsFlyer SDK: + +### Example: + +```cpp +//In case you use manual mode + #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerX::setManualStart(false); + #endif + AppsFlyerX::start(); + // + ``` + + +## API Methods --- @@ -770,3 +805,91 @@ In v6 of AppsFlyer SDK there are some api breaking changes: ### iOS on iOS you need to implement IDFA request pop up and add AppTrackTransparency framework in order for the plugin to work + + +## Send consent for DMA compliance +For a general introduction to DMA consent data, see [here](https://dev.appsflyer.com/hc/docs/send-consent-for-dma-compliance). +The SDK offers two alternative methods for gathering consent data:
+- **Through a Consent Management Platform (CMP)**: If the app uses a CMP that complies with the [Transparency and Consent Framework (TCF) v2.2 protocol](https://iabeurope.eu/tcf-supporting-resources/), the SDK can automatically retrieve the consent details.
+
OR

+- **Through a dedicated SDK API**: Developers can pass Google's required consent data directly to the SDK using a specific API designed for this purpose. +### Use CMP to collect consent data +A CMP compatible with TCF v2.2 collects DMA consent data and stores it in SharedPreferences(Android) or NSUserDefaults(iOS). To enable the SDK to access this data and include it with every event, follow these steps:
+
    +
  1. Call AppsFlyerX::enableTCFDataCollection(true) to instruct the SDK to collect the TCF data from the device. +
  2. Set the the adapter to be manual(see (#manual-start)[manual mode]).
    This will allow us to delay the Conversion call to provide the SDK with the user consent. +
  3. Use the CMP to decide if you need the consent dialog in the current session. +
  4. If needed, show the consent dialog, using the CMP, to capture the user consent decision. Otherwise, go to step 6. +
  5. Get confirmation from the CMP that the user has made their consent decision, and the data is available in SharedPreferences or NSUserDefaults. +
  6. Call start the following way: + +``` + #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerX::setManualStart(false); + #endif + AppsFlyerX::start(); +``` +
+ + #### AppDelegate +``` cpp +bool AppDelegate::applicationDidFinishLaunching() { + + AppsFlyerX::stop(false); + AppsFlyerX::enableTCFDataCollection(true); + AppsFlyerX::setIsDebug(true); + AppsFlyerX::setAppsFlyerDevKey("devkey"); + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + // In case you want to use manual mode. + AppsFlyerX::setManualStart(true); + // + AppsFlyerX::setAppleAppID("appleAppId"); +``` +#### Scene class +- after getting CMP results +```cpp + #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerX::setManualStart(false); + #endif + AppsFlyerX::start(); +``` + + +### Manually collect consent data +If your app does not use a CMP compatible with TCF v2.2, use the SDK API detailed below to provide the consent data directly to the SDK. +
    +
  1. Initialize AppsFlyerX using manual mode. This will allow us to delay the Conversion call in order to provide the SDK with the user consent. +
  2. Determine whether the GDPR applies or not to the user.
    + - If GDPR applies to the user, perform the following: +
      +
    1. Given that GDPR is applicable to the user, determine whether the consent data is already stored for this session. +
        +
      1. If there is no consent data stored, show the consent dialog to capture the user consent decision. +
      2. If there is consent data stored continue to the next step. +
      +
    2. To transfer the consent data to the SDK create an object called AppsFlyerXConsent using the forGDPRUser() method with the following parameters:
      + - hasConsentForDataUsage - Indicates whether the user has consented to use their data for advertising purposes.
      + - hasConsentForAdsPersonalization - Indicates whether the user has consented to use their data for personalized advertising purposes. +
    3. Call AppsFlyerX::setConsentData() with the AppsFlyerXConsent object. +
    4. Call start: + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerX::setManualStart(false); +#endif +AppsFlyerX::start(); + +

    + - If GDPR doesn’t apply to the user perform the following: +
      +
    1. Create an AppsFlyerXConsent object using the forNonGDPRUser() method. This method doesn’t accept any parameters. +
    2. Call AppsFlyerX::setConsentData() with the AppsFlyerXConsent object. +
    3. Call start: + +#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) + AppsFlyerX::setManualStart(false); +#endif +AppsFlyerX::start(); + +
    +
diff --git a/docs/Android_README.md b/docs/Android_README.md index a105a14..0d89045 100644 --- a/docs/Android_README.md +++ b/docs/Android_README.md @@ -54,7 +54,7 @@ list(APPEND GAME_HEADER We prefer to use `gradle` - as the easiest way to stay up to date. Please add the following lines to your gradle app dependencies: ``` -implementation 'com.appsflyer:af-android-sdk:6.10.3' +implementation 'com.appsflyer:af-android-sdk:6.13.0' ``` For Google Install Referrer, please, add also: diff --git a/proj.android-studio/app/build.gradle b/proj.android-studio/app/build.gradle index 7a22276..e57d939 100644 --- a/proj.android-studio/app/build.gradle +++ b/proj.android-studio/app/build.gradle @@ -131,7 +131,7 @@ android.applicationVariants.all { variant -> dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') implementation project(':libcocos2dx') - implementation 'com.appsflyer:af-android-sdk:6.10.3' + implementation 'com.appsflyer:af-android-sdk:6.13.0' implementation 'com.android.installreferrer:installreferrer:2.1' implementation 'com.google.firebase:firebase-messaging:10.0.1' }