@@ -5,6 +5,7 @@ import android.app.Application.ActivityLifecycleCallbacks
55import android.content.Context
66import android.content.Intent
77import android.os.Handler
8+ import android.util.Log
89import com.braze.Braze
910import com.braze.BrazeActivityLifecycleCallbackListener
1011import com.braze.BrazeUser
@@ -23,11 +24,13 @@ import com.mparticle.commerce.CommerceEvent
2324import com.mparticle.commerce.Impression
2425import com.mparticle.commerce.Product
2526import com.mparticle.commerce.Promotion
27+ import com.mparticle.consent.ConsentState
2628import com.mparticle.identity.MParticleUser
2729import com.mparticle.internal.Logger
2830import com.mparticle.kits.CommerceEventUtils.OnAttributeExtracted
2931import com.mparticle.kits.KitIntegration.*
3032import org.json.JSONArray
33+ import org.json.JSONException
3134import org.json.JSONObject
3235import java.math.BigDecimal
3336import java.text.SimpleDateFormat
@@ -38,7 +41,7 @@ import kotlin.collections.HashMap
3841 * mParticle client-side Appboy integration
3942 */
4043open class AppboyKit : KitIntegration (), AttributeListener, CommerceListener,
41- KitIntegration .EventListener , PushListener , IdentityListener {
44+ KitIntegration .EventListener , PushListener , IdentityListener , KitIntegration . UserAttributeListener {
4245
4346 var enableTypeDetection = false
4447 var bundleCommerceEvents = false
@@ -106,7 +109,10 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener,
106109 if (user != null ) {
107110 updateUser(user)
108111 }
109-
112+ val userConsentState = currentUser?.consentState
113+ userConsentState?.let {
114+ setConsent(currentUser.consentState)
115+ }
110116 return null
111117 }
112118
@@ -357,7 +363,146 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener,
357363 })
358364 }
359365
366+ override fun onIncrementUserAttribute (
367+ key : String? ,
368+ incrementedBy : Number ? ,
369+ value : String? ,
370+ user : FilteredMParticleUser ?
371+ ) {
372+ }
373+
374+ override fun onRemoveUserAttribute (key : String? , user : FilteredMParticleUser ? ) {
375+ }
376+
377+ override fun onSetUserAttribute (key : String? , value : Any? , user : FilteredMParticleUser ? ) {
378+ }
379+
380+ override fun onSetUserTag (key : String? , user : FilteredMParticleUser ? ) {
381+ }
382+
383+ override fun onSetUserAttributeList (
384+ attributeKey : String? ,
385+ attributeValueList : MutableList <String >? ,
386+ user : FilteredMParticleUser ?
387+ ) {
388+ }
389+
390+ override fun onSetAllUserAttributes (
391+ userAttributes : MutableMap <String , String >? ,
392+ userAttributeLists : MutableMap <String , MutableList <String >>? ,
393+ user : FilteredMParticleUser ?
394+ ) {
395+ }
396+
360397 override fun supportsAttributeLists (): Boolean = true
398+ override fun onConsentStateUpdated (
399+ oldState : ConsentState ,
400+ newState : ConsentState ,
401+ user : FilteredMParticleUser
402+ ) {
403+ setConsent(newState)
404+ }
405+
406+ private fun setConsent (consentState : ConsentState ) {
407+ val clientConsentSettings = parseToNestedMap(consentState.toString())
408+
409+ parseConsentMapping(settings[consentMappingSDK]).iterator().forEach { currentConsent ->
410+ val isConsentAvailable =
411+ searchKeyInNestedMap(clientConsentSettings, key = currentConsent.key)
412+
413+ if (isConsentAvailable != null ) {
414+ val isConsentGranted: Boolean =
415+ JSONObject (isConsentAvailable.toString()).opt(" consented" ) as Boolean
416+
417+ when (currentConsent.value) {
418+ " google_ad_user_data" -> setConsentValueToBraze(
419+ KEY_GOOGLE_AD_USER_DATA , isConsentGranted
420+ )
421+
422+ " google_ad_personalization" -> setConsentValueToBraze(
423+ KEY_GOOGLE_AD_PERSONALIZATION , isConsentGranted
424+ )
425+
426+ }
427+ }
428+ }
429+ }
430+
431+ private fun setConsentValueToBraze (key : String , value : Boolean ) {
432+ Braze .getInstance(context).getCurrentUser(object : IValueCallback <BrazeUser > {
433+ override fun onSuccess (brazeUser : BrazeUser ) {
434+ brazeUser.setCustomUserAttribute(key, value)
435+ }
436+
437+ override fun onError () {
438+ super .onError()
439+ }
440+ })
441+ }
442+
443+ private fun parseConsentMapping (json : String? ): Map <String , String > {
444+ if (json.isNullOrEmpty()) {
445+ return emptyMap()
446+ }
447+ val jsonWithFormat = json.replace(" \\ " , " " )
448+
449+ return try {
450+ JSONArray (jsonWithFormat)
451+ .let { jsonArray ->
452+ (0 until jsonArray.length())
453+ .associate {
454+ val jsonObject = jsonArray.getJSONObject(it)
455+ val map = jsonObject.getString(" map" )
456+ val value = jsonObject.getString(" value" )
457+ map to value
458+ }
459+ }
460+ } catch (jse: JSONException ) {
461+ Logger .warning(jse, " The Braze kit threw an exception while searching for the configured consent purpose mapping in the current user's consent status." )
462+ emptyMap()
463+ }
464+ }
465+
466+ private fun parseToNestedMap (jsonString : String ): Map <String , Any > {
467+ val topLevelMap = mutableMapOf<String , Any >()
468+ try {
469+ val jsonObject = JSONObject (jsonString)
470+
471+ for (key in jsonObject.keys()) {
472+ val value = jsonObject.get(key)
473+ if (value is JSONObject ) {
474+ topLevelMap[key] = parseToNestedMap(value.toString())
475+ } else {
476+ topLevelMap[key] = value
477+ }
478+ }
479+ } catch (e: Exception ) {
480+ Logger .error(e, " The Braze kit was unable to parse the user's ConsentState, consent may not be set correctly on the Braze SDK" )
481+ }
482+ return topLevelMap
483+ }
484+
485+ private fun searchKeyInNestedMap (map : Map <* , * >, key : Any ): Any? {
486+ if (map.isNullOrEmpty()) {
487+ return null
488+ }
489+ try {
490+ for ((mapKey, mapValue) in map) {
491+ if (mapKey.toString().equals(key.toString(), ignoreCase = true )) {
492+ return mapValue
493+ }
494+ if (mapValue is Map <* , * >) {
495+ val foundValue = searchKeyInNestedMap(mapValue, key)
496+ if (foundValue != null ) {
497+ return foundValue
498+ }
499+ }
500+ }
501+ } catch (e: Exception ) {
502+ Logger .error(e, " The Braze kit threw an exception while searching for the configured consent purpose mapping in the current user's consent status." )
503+ }
504+ return null
505+ }
361506
362507 protected open fun queueDataFlush () {
363508 dataFlushRunnable?.let { dataFlushHandler.removeCallbacks(it) }
@@ -944,6 +1089,11 @@ open class AppboyKit : KitIntegration(), AttributeListener, CommerceListener,
9441089 private const val UNSUBSCRIBED = " unsubscribed"
9451090 private const val SUBSCRIBED = " subscribed"
9461091
1092+ // Constants for Read Consent
1093+ private const val consentMappingSDK = " consentMappingSDK"
1094+ private const val KEY_GOOGLE_AD_USER_DATA = " \$ google_ad_user_data"
1095+ private const val KEY_GOOGLE_AD_PERSONALIZATION = " \$ google_ad_personalization"
1096+
9471097 const val CUSTOM_ATTRIBUTES_KEY = " Attributes"
9481098 const val PRODUCT_KEY = " products"
9491099 const val PROMOTION_KEY = " promotions"
0 commit comments