Skip to content

Commit

Permalink
Add GDPR and CCPA consent APIs to Chartboost Flutter Adapter Plugin
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 702353803
  • Loading branch information
LTPhantom committed Jan 25, 2025
1 parent 21463bf commit 24afe63
Show file tree
Hide file tree
Showing 16 changed files with 893 additions and 41 deletions.
22 changes: 16 additions & 6 deletions packages/mediation/gma_mediation_chartboost/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,19 +54,29 @@ android {

dependencies {
implementation("com.google.ads.mediation:chartboost:9.8.1.0")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.mockito:mockito-core:5.0.0")
testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.6.1'
testImplementation 'androidx.test:core-ktx:1.6.1'
testImplementation 'androidx.test.ext:junit:1.2.1'
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib:2.0.21'
testImplementation 'org.mockito:mockito-core:5.5.0'
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.1.0'
testImplementation 'org.robolectric:robolectric:4.10.3'
}

testOptions {
unitTests.all {
useJUnitPlatform()
useJUnit()

testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
unitTests {
includeAndroidResources = true
unitTests.returnDefaultValues = true
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Autogenerated from Pigeon (v22.6.2), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost

import android.util.Log
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MessageCodec
import io.flutter.plugin.common.StandardMessageCodec
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer

private fun wrapResult(result: Any?): List<Any?> {
return listOf(result)
}

private fun wrapError(exception: Throwable): List<Any?> {
return if (exception is FlutterError) {
listOf(
exception.code,
exception.message,
exception.details
)
} else {
listOf(
exception.javaClass.simpleName,
exception.toString(),
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
)
}
}

/**
* Error class for passing custom error details to Flutter via a thrown PlatformException.
* @property code The error code.
* @property message The error message.
* @property details The error details. Must be a datatype supported by the api codec.
*/
class FlutterError (
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()

enum class ChartboostPrivacyStandard(val raw: Int) {
GDPR(0),
CCPA(1);

companion object {
fun ofRaw(raw: Int): ChartboostPrivacyStandard? {
return values().firstOrNull { it.raw == raw }
}
}
}
private open class ChartboostSDKApiPigeonCodec : StandardMessageCodec() {
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
return when (type) {
129.toByte() -> {
return (readValue(buffer) as Long?)?.let {
ChartboostPrivacyStandard.ofRaw(it.toInt())
}
}
else -> super.readValueOfType(type, buffer)
}
}
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
when (value) {
is ChartboostPrivacyStandard -> {
stream.write(129)
writeValue(stream, value.raw)
}
else -> super.writeValue(stream, value)
}
}
}

/**
* The generated classes set the channels to call the methods in the
* corresponding kotlin ChartboostSDKApi interface and swift ChartboostSDKApi
* protocol from the dart layer.
*
* Generated interface from Pigeon that represents a handler of messages from Flutter.
*/
interface ChartboostSDKApi {
/** Used to configure GDPR consent on the Android or iOS Chartboost SDK */
fun setGDPRConsent(userConsent: Boolean)
/** Used to opt out of the sale of personal information in Chartboost SDK. */
fun setCCPAConsent(userOptIn: Boolean)
/** Used to clear any of the privacy data use consent above. */
fun clearDataUseConsent(privacyStandard: ChartboostPrivacyStandard)

companion object {
/** The codec used by ChartboostSDKApi. */
val codec: MessageCodec<Any?> by lazy {
ChartboostSDKApiPigeonCodec()
}
/** Sets up an instance of `ChartboostSDKApi` to handle messages through the `binaryMessenger`. */
@JvmOverloads
fun setUp(binaryMessenger: BinaryMessenger, api: ChartboostSDKApi?, messageChannelSuffix: String = "") {
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.setGDPRConsent$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val userConsentArg = args[0] as Boolean
val wrapped: List<Any?> = try {
api.setGDPRConsent(userConsentArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.setCCPAConsent$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val userOptInArg = args[0] as Boolean
val wrapped: List<Any?> = try {
api.setCCPAConsent(userOptInArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
run {
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.gma_mediation_chartboost.ChartboostSDKApi.clearDataUseConsent$separatedMessageChannelSuffix", codec)
if (api != null) {
channel.setMessageHandler { message, reply ->
val args = message as List<Any?>
val privacyStandardArg = args[0] as ChartboostPrivacyStandard
val wrapped: List<Any?> = try {
api.clearDataUseConsent(privacyStandardArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
}
reply.reply(wrapped)
}
} else {
channel.setMessageHandler(null)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,55 @@

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost

import android.content.Context
import com.chartboost.sdk.Chartboost
import com.chartboost.sdk.privacy.model.CCPA
import com.chartboost.sdk.privacy.model.GDPR
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding

/** Required to link the Android dependency of the Chartboost Adapter. */
class GmaMediationChartboostPlugin: FlutterPlugin {
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { }
class GmaMediationChartboostPlugin : FlutterPlugin, ActivityAware, ChartboostSDKApi {
private lateinit var context: Context

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { }
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
context = flutterPluginBinding.applicationContext
ChartboostSDKApi.setUp(flutterPluginBinding.binaryMessenger, this)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
ChartboostSDKApi.setUp(binding.binaryMessenger, null)
}

override fun setGDPRConsent(userConsent: Boolean) {
val dataUserConsent =
if (userConsent) GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)
else GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL)
Chartboost.addDataUseConsent(context, dataUserConsent)
}

override fun setCCPAConsent(userOptIn: Boolean) {
val dataUseConsent =
if (userOptIn) CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE) else CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE)
Chartboost.addDataUseConsent(context, dataUseConsent)
}

override fun clearDataUseConsent(privacyStandard: ChartboostPrivacyStandard) {
val dataUseConsent = when (privacyStandard) {
ChartboostPrivacyStandard.GDPR ->
GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)
ChartboostPrivacyStandard.CCPA ->
CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE)
}
Chartboost.clearDataUseConsent(context, dataUseConsent.privacyStandard)
}

override fun onDetachedFromActivity() {}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {}

override fun onDetachedFromActivityForConfigChanges() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,118 @@

package io.flutter.plugins.googlemobileads.mediation.gma_mediation_chartboost

internal class GmaMediationChartboostPluginTest {}
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.chartboost.sdk.Chartboost
import com.chartboost.sdk.privacy.model.CCPA
import com.chartboost.sdk.privacy.model.GDPR
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mockStatic
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock

@RunWith(AndroidJUnit4::class)
internal class GmaMediationChartboostPluginTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private val mockBinaryMessenger = mock<BinaryMessenger>()
private val mockFlutterPluginBinding =
mock<FlutterPlugin.FlutterPluginBinding> {
on { applicationContext } doReturn context
on { binaryMessenger } doReturn mockBinaryMessenger
}

@Test
fun setGDPRConsent_withTrueValue_addsDataUserConsentAsBehavioral() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = GDPR(GDPR.GDPR_CONSENT.BEHAVIORAL)

plugin.setGDPRConsent(true)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun setGDPRConsent_withFalseValue_addsDataUserConsentAsNonBehavioral() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL)

plugin.setGDPRConsent(false)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun setCCPAConsent_withTrueValue_addsDataUserConsentAsOptInSale() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = CCPA(CCPA.CCPA_CONSENT.OPT_IN_SALE)

plugin.setCCPAConsent(true)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun setCCPAConsent_withFalseValue_addsDataUserConsentAsOptOutSale() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedDataUserConsent = CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE)

plugin.setCCPAConsent(false)

mockedchartboostSDKApi.verify {
Chartboost.addDataUseConsent(eq(context), eq(expectedDataUserConsent))
}
}
}

@Test
fun clearDataUseConsent_usesGDPRPrivacyStandard() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedPrivacyStandard = GDPR(GDPR.GDPR_CONSENT.NON_BEHAVIORAL).privacyStandard

plugin.clearDataUseConsent(ChartboostPrivacyStandard.GDPR)

mockedchartboostSDKApi.verify {
Chartboost.clearDataUseConsent(eq(context), eq(expectedPrivacyStandard))
}
}
}

@Test
fun clearDataUseConsent_usesCCPAPrivacyStandard() {
val plugin = GmaMediationChartboostPlugin()
mockStatic(ChartboostSDKApi::class.java).use { mockedchartboostSDKApi ->
plugin.onAttachedToEngine(mockFlutterPluginBinding)
val expectedPrivacyStandard = CCPA(CCPA.CCPA_CONSENT.OPT_OUT_SALE).privacyStandard

plugin.clearDataUseConsent(ChartboostPrivacyStandard.CCPA)

mockedchartboostSDKApi.verify {
Chartboost.clearDataUseConsent(eq(context), eq(expectedPrivacyStandard))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
Loading

0 comments on commit 24afe63

Please sign in to comment.